Added SSE ability to HTTPS method (fusion)

This commit is contained in:
Manas Hejmadi
2025-06-25 20:50:27 +05:30
parent e5b22a24fc
commit 97db38a42d
7 changed files with 202 additions and 16 deletions

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -311,7 +313,14 @@ class CollectionStateNotifier
state = map;
bool noSSL = ref.read(settingsProvider).isSSLDisabled;
var responseRec = await sendHttpRequest(
(Response?, Duration?, String?) responseRec;
HttpResponseModel? respModel;
HistoryRequestModel? historyM;
RequestModel? newRequestModel;
responseRec = (null, null, null);
final stream = await streamHttpRequest(
requestId,
apiType,
substitutedHttpRequestModel,
@ -319,7 +328,78 @@ class CollectionStateNotifier
noSSL: noSSL,
);
late RequestModel newRequestModel;
StreamSubscription? sub;
final completer = Completer();
bool isTextStream = false;
sub = stream.listen((d) async {
if (d == null) return;
isTextStream = ((d.$1 == null && isTextStream) ||
(d.$1 == 'text/event-stream' || d.$1 == 'application/x-ndjson'));
responseRec = (d.$2, d.$3, d.$4);
if (!isTextStream) {
if (completer.isCompleted) return;
completer.complete(responseRec);
await Future.delayed(Duration(milliseconds: 100));
}
if (responseRec.$1 != null) {
responseRec = (
HttpResponse(
responseRec.$1!.body,
responseRec.$1!.statusCode,
request: responseRec.$1!.request,
headers: {
...(responseRec.$1?.headers ?? {}),
'content-type': 'text/event-stream'
},
isRedirect: responseRec.$1!.isRedirect,
reasonPhrase: responseRec.$1!.reasonPhrase,
persistentConnection: responseRec.$1!.persistentConnection,
),
responseRec.$2,
responseRec.$3,
);
}
//----------- MAKE CHANGES --------------
respModel = respModel?.copyWith(sseOutput: [
...(respModel?.sseOutput ?? []),
responseRec.$1!.body,
]);
if (respModel != null) {
final nRM = newRequestModel!.copyWith(
httpResponseModel: respModel,
);
map = {...state!};
map[requestId] = nRM;
state = map;
unsave();
}
//Changing History
if (historyM != null && respModel != null) {
historyM = historyM!.copyWith(
httpResponseModel: respModel!,
);
ref
.read(historyMetaStateNotifier.notifier)
.editHistoryRequest(historyM!);
}
//----------- MAKE CHANGES --------------
if (completer.isCompleted) return;
completer.complete(responseRec);
}, onDone: () {
sub?.cancel();
}, onError: (e) {
print('err: $e');
});
responseRec = await completer.future;
if (responseRec.$1 == null) {
newRequestModel = requestModel.copyWith(
responseStatus: -1,
@ -327,7 +407,7 @@ class CollectionStateNotifier
isWorking: false,
);
} else {
final httpResponseModel = baseHttpResponseModel.fromResponse(
respModel = baseHttpResponseModel.fromResponse(
response: responseRec.$1!,
time: responseRec.$2!,
);
@ -335,11 +415,11 @@ class CollectionStateNotifier
newRequestModel = requestModel.copyWith(
responseStatus: statusCode,
message: kResponseCodeReasons[statusCode],
httpResponseModel: httpResponseModel,
httpResponseModel: respModel,
isWorking: false,
);
String newHistoryId = getNewUuid();
HistoryRequestModel model = HistoryRequestModel(
historyM = HistoryRequestModel(
historyId: newHistoryId,
metaData: HistoryMetaModel(
historyId: newHistoryId,
@ -352,7 +432,7 @@ class CollectionStateNotifier
timeStamp: DateTime.now(),
),
httpRequestModel: substitutedHttpRequestModel,
httpResponseModel: httpResponseModel,
httpResponseModel: respModel!,
preRequestScript: requestModel.preRequestScript,
postRequestScript: requestModel.postRequestScript,
);
@ -372,7 +452,7 @@ class CollectionStateNotifier
},
);
}
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model);
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(historyM!);
}
// update state with response data

View File

@ -90,6 +90,21 @@ class HistoryMetaStateNotifier
await loadHistoryRequest(id);
}
void editHistoryRequest(HistoryRequestModel model) async {
final id = model.historyId;
state = {
...state ?? {},
id: model.metaData,
};
final existingKeys = state?.keys.toList() ?? [];
if (!existingKeys.contains(id)) {
hiveHandler.setHistoryIds([...existingKeys, id]);
}
hiveHandler.setHistoryMeta(id, model.metaData.toJson());
await hiveHandler.setHistoryRequest(id, model.toJson());
await loadHistoryRequest(id);
}
Future<void> clearAllHistory() async {
await hiveHandler.clearAllHistory();
ref.read(selectedHistoryIdStateProvider.notifier).state = null;

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:apidash/models/models.dart';
@ -54,6 +56,22 @@ class ResponseBody extends StatelessWidget {
options.remove(ResponseBodyView.code);
}
// print('reM -> ${responseModel.sseOutput}');
if (responseModel.sseOutput?.isNotEmpty ?? false) {
final modifiedBody = responseModel.sseOutput!.join('\n\n');
print(modifiedBody);
return ResponseBodySuccess(
key: Key("${selectedRequestModel!.id}-response"),
mediaType: mediaType,
options: options,
bytes: utf8.encode(modifiedBody),
body: modifiedBody,
formattedBody: modifiedBody,
highlightLanguage: highlightLanguage,
);
}
return ResponseBodySuccess(
key: Key("${selectedRequestModel!.id}-response"),
mediaType: mediaType,

View File

@ -53,6 +53,7 @@ class HttpResponseModel with _$HttpResponseModel {
String? formattedBody,
@Uint8ListConverter() Uint8List? bodyBytes,
@DurationConverter() Duration? time,
List<String>? sseOutput,
}) = _HttpResponseModel;
factory HttpResponseModel.fromJson(Map<String, Object?> json) =>

View File

@ -30,6 +30,7 @@ mixin _$HttpResponseModel {
Uint8List? get bodyBytes => throw _privateConstructorUsedError;
@DurationConverter()
Duration? get time => throw _privateConstructorUsedError;
List<String>? get sseOutput => throw _privateConstructorUsedError;
/// Serializes this HttpResponseModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -56,6 +57,7 @@ abstract class $HttpResponseModelCopyWith<$Res> {
String? formattedBody,
@Uint8ListConverter() Uint8List? bodyBytes,
@DurationConverter() Duration? time,
List<String>? sseOutput,
});
}
@ -81,6 +83,7 @@ class _$HttpResponseModelCopyWithImpl<$Res, $Val extends HttpResponseModel>
Object? formattedBody = freezed,
Object? bodyBytes = freezed,
Object? time = freezed,
Object? sseOutput = freezed,
}) {
return _then(
_value.copyWith(
@ -112,6 +115,10 @@ class _$HttpResponseModelCopyWithImpl<$Res, $Val extends HttpResponseModel>
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as Duration?,
sseOutput: freezed == sseOutput
? _value.sseOutput
: sseOutput // ignore: cast_nullable_to_non_nullable
as List<String>?,
)
as $Val,
);
@ -135,6 +142,7 @@ abstract class _$$HttpResponseModelImplCopyWith<$Res>
String? formattedBody,
@Uint8ListConverter() Uint8List? bodyBytes,
@DurationConverter() Duration? time,
List<String>? sseOutput,
});
}
@ -159,6 +167,7 @@ class __$$HttpResponseModelImplCopyWithImpl<$Res>
Object? formattedBody = freezed,
Object? bodyBytes = freezed,
Object? time = freezed,
Object? sseOutput = freezed,
}) {
return _then(
_$HttpResponseModelImpl(
@ -190,6 +199,10 @@ class __$$HttpResponseModelImplCopyWithImpl<$Res>
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as Duration?,
sseOutput: freezed == sseOutput
? _value._sseOutput
: sseOutput // ignore: cast_nullable_to_non_nullable
as List<String>?,
),
);
}
@ -207,8 +220,10 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
this.formattedBody,
@Uint8ListConverter() this.bodyBytes,
@DurationConverter() this.time,
final List<String>? sseOutput,
}) : _headers = headers,
_requestHeaders = requestHeaders,
_sseOutput = sseOutput,
super._();
factory _$HttpResponseModelImpl.fromJson(Map<String, dynamic> json) =>
@ -246,10 +261,19 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
@override
@DurationConverter()
final Duration? time;
final List<String>? _sseOutput;
@override
List<String>? get sseOutput {
final value = _sseOutput;
if (value == null) return null;
if (_sseOutput is EqualUnmodifiableListView) return _sseOutput;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString() {
return 'HttpResponseModel(statusCode: $statusCode, headers: $headers, requestHeaders: $requestHeaders, body: $body, formattedBody: $formattedBody, bodyBytes: $bodyBytes, time: $time)';
return 'HttpResponseModel(statusCode: $statusCode, headers: $headers, requestHeaders: $requestHeaders, body: $body, formattedBody: $formattedBody, bodyBytes: $bodyBytes, time: $time, sseOutput: $sseOutput)';
}
@override
@ -268,7 +292,11 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
(identical(other.formattedBody, formattedBody) ||
other.formattedBody == formattedBody) &&
const DeepCollectionEquality().equals(other.bodyBytes, bodyBytes) &&
(identical(other.time, time) || other.time == time));
(identical(other.time, time) || other.time == time) &&
const DeepCollectionEquality().equals(
other._sseOutput,
_sseOutput,
));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -282,6 +310,7 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
formattedBody,
const DeepCollectionEquality().hash(bodyBytes),
time,
const DeepCollectionEquality().hash(_sseOutput),
);
/// Create a copy of HttpResponseModel
@ -310,6 +339,7 @@ abstract class _HttpResponseModel extends HttpResponseModel {
final String? formattedBody,
@Uint8ListConverter() final Uint8List? bodyBytes,
@DurationConverter() final Duration? time,
final List<String>? sseOutput,
}) = _$HttpResponseModelImpl;
const _HttpResponseModel._() : super._();
@ -332,6 +362,8 @@ abstract class _HttpResponseModel extends HttpResponseModel {
@override
@DurationConverter()
Duration? get time;
@override
List<String>? get sseOutput;
/// Create a copy of HttpResponseModel
/// with the given fields replaced by the non-null parameter values.

View File

@ -21,6 +21,9 @@ _$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) =>
json['bodyBytes'] as List<int>?,
),
time: const DurationConverter().fromJson((json['time'] as num?)?.toInt()),
sseOutput: (json['sseOutput'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$$HttpResponseModelImplToJson(
@ -33,4 +36,5 @@ Map<String, dynamic> _$$HttpResponseModelImplToJson(
'formattedBody': instance.formattedBody,
'bodyBytes': const Uint8ListConverter().toJson(instance.bodyBytes),
'time': const DurationConverter().toJson(instance.time),
'sseOutput': instance.sseOutput,
};

View File

@ -165,14 +165,18 @@ http.Request prepareHttpRequest({
return request;
}
Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
Future<Stream<(String? cT, HttpResponse? resp, Duration? dur, String? err)?>>
streamHttpRequest(
String requestId,
APIType apiType,
HttpRequestModel requestModel, {
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
bool noSSL = false,
}) async {
final controller = StreamController<(String?, Duration?, String?)?>();
final controller =
StreamController<
(String? cT, HttpResponse? resp, Duration? dur, String? err)?
>();
StreamSubscription<String?>? subscription;
final stopwatch = Stopwatch()..start();
@ -186,10 +190,10 @@ Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
Future<void> handleError(dynamic error) async {
await Future.microtask(() {});
if (httpClientManager.wasRequestCancelled(requestId)) {
controller.add((null, null, kMsgRequestCancelled));
controller.add((null, null, null, kMsgRequestCancelled));
httpClientManager.removeCancelledRequest(requestId);
} else {
controller.add((null, null, error.toString()));
controller.add((null, null, null, error.toString()));
}
await cleanup();
}
@ -200,7 +204,7 @@ Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
};
if (httpClientManager.wasRequestCancelled(requestId)) {
controller.add((null, null, kMsgRequestCancelled));
controller.add((null, null, null, kMsgRequestCancelled));
httpClientManager.removeCancelledRequest(requestId);
controller.close();
return controller.stream;
@ -243,8 +247,25 @@ Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
final streamedResponse = await client.send(multipart);
final stream = streamTextResponse(streamedResponse);
print(streamedResponse.headers['content-type']);
subscription = stream.listen(
(data) => controller.add((data, stopwatch.elapsed, null)),
(data) => controller.add((
streamedResponse.headers['content-type'].toString(),
data == null
? null
: HttpResponse.bytes(
utf8.encode(data),
streamedResponse.statusCode,
request: streamedResponse.request,
headers: streamedResponse.headers,
isRedirect: streamedResponse.isRedirect,
persistentConnection: streamedResponse.persistentConnection,
reasonPhrase: streamedResponse.reasonPhrase,
),
stopwatch.elapsed,
null,
)),
onDone: () => cleanup(),
onError: handleError,
);
@ -279,7 +300,22 @@ Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
subscription = stream.listen(
(data) {
if (!controller.isClosed) {
controller.add((data, stopwatch.elapsed, null));
controller.add((
streamedResponse.headers['content-type'].toString(),
data == null
? null
: HttpResponse.bytes(
utf8.encode(data),
streamedResponse.statusCode,
request: streamedResponse.request,
headers: streamedResponse.headers,
isRedirect: streamedResponse.isRedirect,
persistentConnection: streamedResponse.persistentConnection,
reasonPhrase: streamedResponse.reasonPhrase,
),
stopwatch.elapsed,
null,
));
}
},
onDone: () => cleanup(),