send static method

Future<UHttpClientResponse> send({
  1. required String method,
  2. required String endpoint,
  3. required dynamic onSuccess(
    1. Response
    ),
  4. required dynamic onError(
    1. Response
    ),
  5. required dynamic onException(
    1. String
    ),
  6. Map<String, String>? headers,
  7. Map<String, dynamic>? queryParams,
  8. dynamic body,
  9. URequestBodyType bodyType = URequestBodyType.json,
  10. String? noNetworkMessage,
  11. String? unexpectedErrorMessage,
  12. bool offline = false,
  13. Duration? cacheDuration,
  14. int retryAmount = 3,
  15. Duration timeout = const Duration(seconds: 30),
  16. void onProgress(
    1. int percent
    )?,
  17. bool isRetryAfterRefresh = false,
})

Implementation

static Future<UHttpClientResponse> send({
  required String method,
  required String endpoint,
  required Function(Response) onSuccess,
  required Function(Response) onError,
  required Function(String) onException,
  Map<String, String>? headers,
  Map<String, dynamic>? queryParams,
  dynamic body,
  URequestBodyType bodyType = URequestBodyType.json,
  String? noNetworkMessage,
  String? unexpectedErrorMessage,
  bool offline = false,
  Duration? cacheDuration,
  int retryAmount = 3,
  Duration timeout = const Duration(seconds: 30),
  void Function(int percent)? onProgress,
  bool isRetryAfterRefresh = false,
}) async {
  int lastPercent = -1;
  void report(num percent) {
    if (onProgress == null) return;
    final int p = percent.clamp(0, 100).toInt();
    if (p <= lastPercent) return;
    lastPercent = p;
    onProgress(p);
  }

  final bool hasNetworkConnection = await UNetwork.hasAnyConnection();

  if (!hasNetworkConnection && offline == false) {
    final String message = noNetworkMessage ?? U.s.connectionToNetworkWasNotPossible;
    onException(message);
    return UHttpClientResponse(exception: message);
  }

  final Uri uri = _buildUri(endpoint, queryParams);
  final bool cacheEnabled = offline || cacheDuration != null;
  final String cacheKey = cacheEnabled ? _cacheKey(method, uri, body) : "";

  if (offline) {
    final String? cachedData = await _readCache(cacheKey);
    if (cachedData != null) {
      final Response response = Response(cachedData, 200, request: Request(method, uri));
      onSuccess(response);
      return UHttpClientResponse(response: cachedData);
    }
  }

  final Response response;
  try {
    final Request request = Request(method, uri);
    if (headers != null) request.headers.addAll(headers);

    if (body != null) {
      if (bodyType == URequestBodyType.json) {
        if (body is Map) {
          request.body = jsonEncode(removeNullEntries(body));
          request.headers["Content-Type"] = "application/json";
          request.headers["Locale"] = UApp.locale();
          request.headers["Timezone"] = UTimezone.getLocalTimezone();
        } else if (body is String) {
          request.body = body;
        } else if (body is List<int>) {
          request.bodyBytes = body;
        }
      } else if (bodyType == URequestBodyType.formData && body is Map<String, dynamic>) {
        final Map<String, String> formFields = <String, String>{};
        body.forEach((String key, dynamic value) {
          if (value != null) {
            formFields[key] = value.toString();
          }
        });
        request.bodyFields = formFields;
        request.headers["Content-Type"] = "application/x-www-form-urlencoded";
      }
    }

    final List<int> uploadBytes = request.bodyBytes;
    final bool hasUpload = uploadBytes.length >= 64 * 1024;
    final int uploadWeight = hasUpload ? 50 : 0;
    final int downloadBand = 100 - uploadWeight;

    final BaseRequest outgoing = !hasUpload || onProgress == null
        ? request
        : (_UProgressRequest(method, uri, uploadBytes, (int uploadPercent) => report((uploadPercent / 100 * uploadWeight).round()))
            ..headers.addAll(request.headers)
            ..contentLength = uploadBytes.length);

    final StreamedResponse streamed = await _client.send(outgoing).timeout(timeout);
    report(uploadWeight);

    final int? totalBytes = streamed.contentLength;
    final BytesBuilder receivedBuilder = BytesBuilder(copy: false);
    int receivedCount = 0;

    Timer? estimateTicker;
    if (onProgress != null && (totalBytes == null || totalBytes <= 0)) {
      const int tauMs = 7000;
      final Stopwatch sw = Stopwatch()..start();
      estimateTicker = Timer.periodic(const Duration(milliseconds: 200), (Timer _) {
        final double frac = sw.elapsedMilliseconds / (sw.elapsedMilliseconds + tauMs);
        report(uploadWeight + (frac * downloadBand).round().clamp(0, downloadBand - 1));
      });
    }

    try {
      await for (final List<int> chunk in streamed.stream) {
        receivedBuilder.add(chunk);
        receivedCount += chunk.length;
        if (totalBytes != null && totalBytes > 0) report(uploadWeight + (receivedCount / totalBytes * downloadBand).round());
      }
    } finally {
      estimateTicker?.cancel();
    }
    report(100);

    response = Response.bytes(
      receivedBuilder.takeBytes(),
      streamed.statusCode,
      request: streamed.request,
      headers: streamed.headers,
      reasonPhrase: streamed.reasonPhrase,
    );
  } catch (e, stack) {
    developer.log(e.toString(), stackTrace: stack);
    if (retryAmount > 0) {
      final int attempt = retryAmount < 1 ? 1 : (4 - retryAmount).clamp(1, 4);
      await Future<void>.delayed(Duration(milliseconds: 300 * attempt));
      return send(
        method: method,
        endpoint: endpoint,
        onSuccess: onSuccess,
        onError: onError,
        onException: onException,
        headers: headers,
        offline: offline,
        cacheDuration: cacheDuration,
        body: body,
        bodyType: bodyType,
        noNetworkMessage: noNetworkMessage,
        queryParams: queryParams,
        retryAmount: retryAmount - 1,
        unexpectedErrorMessage: unexpectedErrorMessage,
        timeout: timeout,
        onProgress: onProgress,
        isRetryAfterRefresh: isRetryAfterRefresh,
      );
    } else {
      final String message = unexpectedErrorMessage ?? U.s.unexpectedErrorPleaseTryAgain;
      onException(message);
      return UHttpClientResponse(exception: message);
    }
  }

  if (kDebugMode) response.prettyLog(params: jsonEncode(body));

  try {
    if (response.statusCode >= 200 && response.statusCode <= 299) {
      if (cacheEnabled) await _writeCache(cacheKey, response.body, cacheDuration);
      onSuccess(response);
      return UHttpClientResponse(response: response.body);
    } else if (response.statusCode == Usc.expiredToken.number && !isRetryAfterRefresh && !endpoint.contains("/auth/")) {
      final bool refreshed = await _refreshToken();
      if (refreshed) {
        final dynamic retryBody = body is Map ? (Map<String, dynamic>.from(body)..["token"] = ULocalStorage.getToken()) : body;
        return await send(
          method: method,
          endpoint: endpoint,
          onSuccess: onSuccess,
          onError: onError,
          onException: onException,
          headers: headers,
          queryParams: queryParams,
          body: retryBody,
          bodyType: bodyType,
          noNetworkMessage: noNetworkMessage,
          unexpectedErrorMessage: unexpectedErrorMessage,
          offline: offline,
          cacheDuration: cacheDuration,
          retryAmount: retryAmount,
          timeout: timeout,
          onProgress: onProgress,
          isRetryAfterRefresh: true,
        );
      }
      await onAuthFailed?.call();
      onError(response);
      return UHttpClientResponse(error: response.body);
    } else {
      onError(response);
      return UHttpClientResponse(error: response.body);
    }
  } catch (e, stack) {
    developer.log(e.toString(), stackTrace: stack);
    final String message = unexpectedErrorMessage ?? U.s.unexpectedErrorPleaseTryAgain;
    onException(message);
    return UHttpClientResponse(exception: message);
  }
}