diff --git a/lib/consts.dart b/lib/consts.dart index 595aa69e7..0e11afaf3 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -293,6 +293,19 @@ const kResponseCodeReasons = { 508: 'Loop Detected', 510: 'Not Extended', 511: 'Network Authentication Required', + // 1000s - WebSocket Error + 1000: 'Normal Closure', + 1001: 'Going Away', + 1002: 'Protocol Error', + 1003: 'Unsupported Data', + 1005: 'No Status Received', + 1006: 'Abnormal Closure', + 1007: 'Invalid Frame Payload Data', + 1008: 'Policy Violation', + 1009: 'Message Too Big', + 1010: 'Missing Mandatory Extension', + 1011: 'Internal Server Error (WebSocket)', + 1015: 'TLS Handshake Failed', }; Map kHttpHeadersMap = { @@ -433,6 +446,8 @@ const kLabelCancel = "Cancel"; const kLabelOk = "Ok"; const kLabelImport = "Import"; const kUntitled = "untitled"; +const kLabelConnect = "Connect"; +const kLabelDisconnect = "Disconnect"; // Request Pane const kLabelRequest = "Request"; const kLabelHideCode = "Hide Code"; @@ -441,6 +456,7 @@ const kLabelURLParams = "URL Params"; const kLabelHeaders = "Headers"; const kLabelBody = "Body"; const kLabelQuery = "Query"; +const kLabelMessage = "Message"; const kNameCheckbox = "Checkbox"; const kNameURLParam = "URL Parameter"; const kNameHeader = "Header Name"; @@ -459,6 +475,7 @@ const kHintContent = "Enter content"; const kHintText = "Enter text"; const kHintJson = "Enter JSON"; const kHintQuery = "Enter Query"; +const kHintMessage = "Enter message"; // Response Pane const kLabelNotSent = "Not Sent"; const kLabelResponse = "Response"; diff --git a/lib/models/history_meta_model.g.dart b/lib/models/history_meta_model.g.dart index a2b2b7b1d..b8e14cedb 100644 --- a/lib/models/history_meta_model.g.dart +++ b/lib/models/history_meta_model.g.dart @@ -35,6 +35,7 @@ Map _$$HistoryMetaModelImplToJson( const _$APITypeEnumMap = { APIType.rest: 'rest', APIType.graphql: 'graphql', + APIType.webSocket: 'webSocket', }; const _$HTTPVerbEnumMap = { diff --git a/lib/models/history_request_model.dart b/lib/models/history_request_model.dart index a895e945c..7036dbd72 100644 --- a/lib/models/history_request_model.dart +++ b/lib/models/history_request_model.dart @@ -16,6 +16,9 @@ class HistoryRequestModel with _$HistoryRequestModel { required HistoryMetaModel metaData, required HttpRequestModel httpRequestModel, required HttpResponseModel httpResponseModel, + required WebSocketRequestModel? webSocketRequestModel, + required WebSocketResponseModel? webSocketResponseModel, + }) = _HistoryRequestModel; factory HistoryRequestModel.fromJson(Map json) => diff --git a/lib/models/history_request_model.freezed.dart b/lib/models/history_request_model.freezed.dart index bb7e94924..4dabe63ca 100644 --- a/lib/models/history_request_model.freezed.dart +++ b/lib/models/history_request_model.freezed.dart @@ -24,6 +24,10 @@ mixin _$HistoryRequestModel { HistoryMetaModel get metaData => throw _privateConstructorUsedError; HttpRequestModel get httpRequestModel => throw _privateConstructorUsedError; HttpResponseModel get httpResponseModel => throw _privateConstructorUsedError; + WebSocketRequestModel? get webSocketRequestModel => + throw _privateConstructorUsedError; + WebSocketResponseModel? get webSocketResponseModel => + throw _privateConstructorUsedError; /// Serializes this HistoryRequestModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -45,11 +49,15 @@ abstract class $HistoryRequestModelCopyWith<$Res> { {String historyId, HistoryMetaModel metaData, HttpRequestModel httpRequestModel, - HttpResponseModel httpResponseModel}); + HttpResponseModel httpResponseModel, + WebSocketRequestModel? webSocketRequestModel, + WebSocketResponseModel? webSocketResponseModel}); $HistoryMetaModelCopyWith<$Res> get metaData; $HttpRequestModelCopyWith<$Res> get httpRequestModel; $HttpResponseModelCopyWith<$Res> get httpResponseModel; + $WebSocketRequestModelCopyWith<$Res>? get webSocketRequestModel; + $WebSocketResponseModelCopyWith<$Res>? get webSocketResponseModel; } /// @nodoc @@ -71,6 +79,8 @@ class _$HistoryRequestModelCopyWithImpl<$Res, $Val extends HistoryRequestModel> Object? metaData = null, Object? httpRequestModel = null, Object? httpResponseModel = null, + Object? webSocketRequestModel = freezed, + Object? webSocketResponseModel = freezed, }) { return _then(_value.copyWith( historyId: null == historyId @@ -89,6 +99,14 @@ class _$HistoryRequestModelCopyWithImpl<$Res, $Val extends HistoryRequestModel> ? _value.httpResponseModel : httpResponseModel // ignore: cast_nullable_to_non_nullable as HttpResponseModel, + webSocketRequestModel: freezed == webSocketRequestModel + ? _value.webSocketRequestModel + : webSocketRequestModel // ignore: cast_nullable_to_non_nullable + as WebSocketRequestModel?, + webSocketResponseModel: freezed == webSocketResponseModel + ? _value.webSocketResponseModel + : webSocketResponseModel // ignore: cast_nullable_to_non_nullable + as WebSocketResponseModel?, ) as $Val); } @@ -121,6 +139,36 @@ class _$HistoryRequestModelCopyWithImpl<$Res, $Val extends HistoryRequestModel> return _then(_value.copyWith(httpResponseModel: value) as $Val); }); } + + /// Create a copy of HistoryRequestModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $WebSocketRequestModelCopyWith<$Res>? get webSocketRequestModel { + if (_value.webSocketRequestModel == null) { + return null; + } + + return $WebSocketRequestModelCopyWith<$Res>(_value.webSocketRequestModel!, + (value) { + return _then(_value.copyWith(webSocketRequestModel: value) as $Val); + }); + } + + /// Create a copy of HistoryRequestModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $WebSocketResponseModelCopyWith<$Res>? get webSocketResponseModel { + if (_value.webSocketResponseModel == null) { + return null; + } + + return $WebSocketResponseModelCopyWith<$Res>(_value.webSocketResponseModel!, + (value) { + return _then(_value.copyWith(webSocketResponseModel: value) as $Val); + }); + } } /// @nodoc @@ -135,7 +183,9 @@ abstract class _$$HistoryRequestModelImplCopyWith<$Res> {String historyId, HistoryMetaModel metaData, HttpRequestModel httpRequestModel, - HttpResponseModel httpResponseModel}); + HttpResponseModel httpResponseModel, + WebSocketRequestModel? webSocketRequestModel, + WebSocketResponseModel? webSocketResponseModel}); @override $HistoryMetaModelCopyWith<$Res> get metaData; @@ -143,6 +193,10 @@ abstract class _$$HistoryRequestModelImplCopyWith<$Res> $HttpRequestModelCopyWith<$Res> get httpRequestModel; @override $HttpResponseModelCopyWith<$Res> get httpResponseModel; + @override + $WebSocketRequestModelCopyWith<$Res>? get webSocketRequestModel; + @override + $WebSocketResponseModelCopyWith<$Res>? get webSocketResponseModel; } /// @nodoc @@ -162,6 +216,8 @@ class __$$HistoryRequestModelImplCopyWithImpl<$Res> Object? metaData = null, Object? httpRequestModel = null, Object? httpResponseModel = null, + Object? webSocketRequestModel = freezed, + Object? webSocketResponseModel = freezed, }) { return _then(_$HistoryRequestModelImpl( historyId: null == historyId @@ -180,6 +236,14 @@ class __$$HistoryRequestModelImplCopyWithImpl<$Res> ? _value.httpResponseModel : httpResponseModel // ignore: cast_nullable_to_non_nullable as HttpResponseModel, + webSocketRequestModel: freezed == webSocketRequestModel + ? _value.webSocketRequestModel + : webSocketRequestModel // ignore: cast_nullable_to_non_nullable + as WebSocketRequestModel?, + webSocketResponseModel: freezed == webSocketResponseModel + ? _value.webSocketResponseModel + : webSocketResponseModel // ignore: cast_nullable_to_non_nullable + as WebSocketResponseModel?, )); } } @@ -192,7 +256,9 @@ class _$HistoryRequestModelImpl implements _HistoryRequestModel { {required this.historyId, required this.metaData, required this.httpRequestModel, - required this.httpResponseModel}); + required this.httpResponseModel, + required this.webSocketRequestModel, + required this.webSocketResponseModel}); factory _$HistoryRequestModelImpl.fromJson(Map json) => _$$HistoryRequestModelImplFromJson(json); @@ -205,10 +271,14 @@ class _$HistoryRequestModelImpl implements _HistoryRequestModel { final HttpRequestModel httpRequestModel; @override final HttpResponseModel httpResponseModel; + @override + final WebSocketRequestModel? webSocketRequestModel; + @override + final WebSocketResponseModel? webSocketResponseModel; @override String toString() { - return 'HistoryRequestModel(historyId: $historyId, metaData: $metaData, httpRequestModel: $httpRequestModel, httpResponseModel: $httpResponseModel)'; + return 'HistoryRequestModel(historyId: $historyId, metaData: $metaData, httpRequestModel: $httpRequestModel, httpResponseModel: $httpResponseModel, webSocketRequestModel: $webSocketRequestModel, webSocketResponseModel: $webSocketResponseModel)'; } @override @@ -223,13 +293,23 @@ class _$HistoryRequestModelImpl implements _HistoryRequestModel { (identical(other.httpRequestModel, httpRequestModel) || other.httpRequestModel == httpRequestModel) && (identical(other.httpResponseModel, httpResponseModel) || - other.httpResponseModel == httpResponseModel)); + other.httpResponseModel == httpResponseModel) && + (identical(other.webSocketRequestModel, webSocketRequestModel) || + other.webSocketRequestModel == webSocketRequestModel) && + (identical(other.webSocketResponseModel, webSocketResponseModel) || + other.webSocketResponseModel == webSocketResponseModel)); } @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( - runtimeType, historyId, metaData, httpRequestModel, httpResponseModel); + runtimeType, + historyId, + metaData, + httpRequestModel, + httpResponseModel, + webSocketRequestModel, + webSocketResponseModel); /// Create a copy of HistoryRequestModel /// with the given fields replaced by the non-null parameter values. @@ -253,7 +333,9 @@ abstract class _HistoryRequestModel implements HistoryRequestModel { {required final String historyId, required final HistoryMetaModel metaData, required final HttpRequestModel httpRequestModel, - required final HttpResponseModel httpResponseModel}) = + required final HttpResponseModel httpResponseModel, + required final WebSocketRequestModel? webSocketRequestModel, + required final WebSocketResponseModel? webSocketResponseModel}) = _$HistoryRequestModelImpl; factory _HistoryRequestModel.fromJson(Map json) = @@ -267,6 +349,10 @@ abstract class _HistoryRequestModel implements HistoryRequestModel { HttpRequestModel get httpRequestModel; @override HttpResponseModel get httpResponseModel; + @override + WebSocketRequestModel? get webSocketRequestModel; + @override + WebSocketResponseModel? get webSocketResponseModel; /// Create a copy of HistoryRequestModel /// with the given fields replaced by the non-null parameter values. diff --git a/lib/models/history_request_model.g.dart b/lib/models/history_request_model.g.dart index 830d7a4cf..f3aad531f 100644 --- a/lib/models/history_request_model.g.dart +++ b/lib/models/history_request_model.g.dart @@ -15,6 +15,14 @@ _$HistoryRequestModelImpl _$$HistoryRequestModelImplFromJson(Map json) => Map.from(json['httpRequestModel'] as Map)), httpResponseModel: HttpResponseModel.fromJson( Map.from(json['httpResponseModel'] as Map)), + webSocketRequestModel: json['webSocketRequestModel'] == null + ? null + : WebSocketRequestModel.fromJson( + Map.from(json['webSocketRequestModel'] as Map)), + webSocketResponseModel: json['webSocketResponseModel'] == null + ? null + : WebSocketResponseModel.fromJson( + Map.from(json['webSocketResponseModel'] as Map)), ); Map _$$HistoryRequestModelImplToJson( @@ -24,4 +32,6 @@ Map _$$HistoryRequestModelImplToJson( 'metaData': instance.metaData.toJson(), 'httpRequestModel': instance.httpRequestModel.toJson(), 'httpResponseModel': instance.httpResponseModel.toJson(), + 'webSocketRequestModel': instance.webSocketRequestModel?.toJson(), + 'webSocketResponseModel': instance.webSocketResponseModel?.toJson(), }; diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index bb9eefaef..587a8d38e 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -1,7 +1,5 @@ import 'package:apidash_core/apidash_core.dart'; - part 'request_model.freezed.dart'; - part 'request_model.g.dart'; @freezed @@ -17,9 +15,11 @@ class RequestModel with _$RequestModel { @Default("") String description, @JsonKey(includeToJson: false) @Default(0) requestTabIndex, HttpRequestModel? httpRequestModel, + WebSocketRequestModel? webSocketRequestModel, int? responseStatus, String? message, HttpResponseModel? httpResponseModel, + WebSocketResponseModel? webSocketResponseModel, @JsonKey(includeToJson: false) @Default(false) bool isWorking, @JsonKey(includeToJson: false) DateTime? sendingTime, }) = _RequestModel; diff --git a/lib/models/request_model.freezed.dart b/lib/models/request_model.freezed.dart index b237e3726..15da0e7b6 100644 --- a/lib/models/request_model.freezed.dart +++ b/lib/models/request_model.freezed.dart @@ -27,10 +27,14 @@ mixin _$RequestModel { @JsonKey(includeToJson: false) dynamic get requestTabIndex => throw _privateConstructorUsedError; HttpRequestModel? get httpRequestModel => throw _privateConstructorUsedError; + WebSocketRequestModel? get webSocketRequestModel => + throw _privateConstructorUsedError; int? get responseStatus => throw _privateConstructorUsedError; String? get message => throw _privateConstructorUsedError; HttpResponseModel? get httpResponseModel => throw _privateConstructorUsedError; + WebSocketResponseModel? get webSocketResponseModel => + throw _privateConstructorUsedError; @JsonKey(includeToJson: false) bool get isWorking => throw _privateConstructorUsedError; @JsonKey(includeToJson: false) @@ -59,14 +63,18 @@ abstract class $RequestModelCopyWith<$Res> { String description, @JsonKey(includeToJson: false) dynamic requestTabIndex, HttpRequestModel? httpRequestModel, + WebSocketRequestModel? webSocketRequestModel, int? responseStatus, String? message, HttpResponseModel? httpResponseModel, + WebSocketResponseModel? webSocketResponseModel, @JsonKey(includeToJson: false) bool isWorking, @JsonKey(includeToJson: false) DateTime? sendingTime}); $HttpRequestModelCopyWith<$Res>? get httpRequestModel; + $WebSocketRequestModelCopyWith<$Res>? get webSocketRequestModel; $HttpResponseModelCopyWith<$Res>? get httpResponseModel; + $WebSocketResponseModelCopyWith<$Res>? get webSocketResponseModel; } /// @nodoc @@ -90,9 +98,11 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel> Object? description = null, Object? requestTabIndex = freezed, Object? httpRequestModel = freezed, + Object? webSocketRequestModel = freezed, Object? responseStatus = freezed, Object? message = freezed, Object? httpResponseModel = freezed, + Object? webSocketResponseModel = freezed, Object? isWorking = null, Object? sendingTime = freezed, }) { @@ -121,6 +131,10 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel> ? _value.httpRequestModel : httpRequestModel // ignore: cast_nullable_to_non_nullable as HttpRequestModel?, + webSocketRequestModel: freezed == webSocketRequestModel + ? _value.webSocketRequestModel + : webSocketRequestModel // ignore: cast_nullable_to_non_nullable + as WebSocketRequestModel?, responseStatus: freezed == responseStatus ? _value.responseStatus : responseStatus // ignore: cast_nullable_to_non_nullable @@ -133,6 +147,10 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel> ? _value.httpResponseModel : httpResponseModel // ignore: cast_nullable_to_non_nullable as HttpResponseModel?, + webSocketResponseModel: freezed == webSocketResponseModel + ? _value.webSocketResponseModel + : webSocketResponseModel // ignore: cast_nullable_to_non_nullable + as WebSocketResponseModel?, isWorking: null == isWorking ? _value.isWorking : isWorking // ignore: cast_nullable_to_non_nullable @@ -158,6 +176,21 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel> }); } + /// Create a copy of RequestModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $WebSocketRequestModelCopyWith<$Res>? get webSocketRequestModel { + if (_value.webSocketRequestModel == null) { + return null; + } + + return $WebSocketRequestModelCopyWith<$Res>(_value.webSocketRequestModel!, + (value) { + return _then(_value.copyWith(webSocketRequestModel: value) as $Val); + }); + } + /// Create a copy of RequestModel /// with the given fields replaced by the non-null parameter values. @override @@ -171,6 +204,21 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel> return _then(_value.copyWith(httpResponseModel: value) as $Val); }); } + + /// Create a copy of RequestModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $WebSocketResponseModelCopyWith<$Res>? get webSocketResponseModel { + if (_value.webSocketResponseModel == null) { + return null; + } + + return $WebSocketResponseModelCopyWith<$Res>(_value.webSocketResponseModel!, + (value) { + return _then(_value.copyWith(webSocketResponseModel: value) as $Val); + }); + } } /// @nodoc @@ -188,16 +236,22 @@ abstract class _$$RequestModelImplCopyWith<$Res> String description, @JsonKey(includeToJson: false) dynamic requestTabIndex, HttpRequestModel? httpRequestModel, + WebSocketRequestModel? webSocketRequestModel, int? responseStatus, String? message, HttpResponseModel? httpResponseModel, + WebSocketResponseModel? webSocketResponseModel, @JsonKey(includeToJson: false) bool isWorking, @JsonKey(includeToJson: false) DateTime? sendingTime}); @override $HttpRequestModelCopyWith<$Res>? get httpRequestModel; @override + $WebSocketRequestModelCopyWith<$Res>? get webSocketRequestModel; + @override $HttpResponseModelCopyWith<$Res>? get httpResponseModel; + @override + $WebSocketResponseModelCopyWith<$Res>? get webSocketResponseModel; } /// @nodoc @@ -219,9 +273,11 @@ class __$$RequestModelImplCopyWithImpl<$Res> Object? description = null, Object? requestTabIndex = freezed, Object? httpRequestModel = freezed, + Object? webSocketRequestModel = freezed, Object? responseStatus = freezed, Object? message = freezed, Object? httpResponseModel = freezed, + Object? webSocketResponseModel = freezed, Object? isWorking = null, Object? sendingTime = freezed, }) { @@ -249,6 +305,10 @@ class __$$RequestModelImplCopyWithImpl<$Res> ? _value.httpRequestModel : httpRequestModel // ignore: cast_nullable_to_non_nullable as HttpRequestModel?, + webSocketRequestModel: freezed == webSocketRequestModel + ? _value.webSocketRequestModel + : webSocketRequestModel // ignore: cast_nullable_to_non_nullable + as WebSocketRequestModel?, responseStatus: freezed == responseStatus ? _value.responseStatus : responseStatus // ignore: cast_nullable_to_non_nullable @@ -261,6 +321,10 @@ class __$$RequestModelImplCopyWithImpl<$Res> ? _value.httpResponseModel : httpResponseModel // ignore: cast_nullable_to_non_nullable as HttpResponseModel?, + webSocketResponseModel: freezed == webSocketResponseModel + ? _value.webSocketResponseModel + : webSocketResponseModel // ignore: cast_nullable_to_non_nullable + as WebSocketResponseModel?, isWorking: null == isWorking ? _value.isWorking : isWorking // ignore: cast_nullable_to_non_nullable @@ -284,9 +348,11 @@ class _$RequestModelImpl implements _RequestModel { this.description = "", @JsonKey(includeToJson: false) this.requestTabIndex = 0, this.httpRequestModel, + this.webSocketRequestModel, this.responseStatus, this.message, this.httpResponseModel, + this.webSocketResponseModel, @JsonKey(includeToJson: false) this.isWorking = false, @JsonKey(includeToJson: false) this.sendingTime}); @@ -310,12 +376,16 @@ class _$RequestModelImpl implements _RequestModel { @override final HttpRequestModel? httpRequestModel; @override + final WebSocketRequestModel? webSocketRequestModel; + @override final int? responseStatus; @override final String? message; @override final HttpResponseModel? httpResponseModel; @override + final WebSocketResponseModel? webSocketResponseModel; + @override @JsonKey(includeToJson: false) final bool isWorking; @override @@ -324,7 +394,7 @@ class _$RequestModelImpl implements _RequestModel { @override String toString() { - return 'RequestModel(id: $id, apiType: $apiType, name: $name, description: $description, requestTabIndex: $requestTabIndex, httpRequestModel: $httpRequestModel, responseStatus: $responseStatus, message: $message, httpResponseModel: $httpResponseModel, isWorking: $isWorking, sendingTime: $sendingTime)'; + return 'RequestModel(id: $id, apiType: $apiType, name: $name, description: $description, requestTabIndex: $requestTabIndex, httpRequestModel: $httpRequestModel, webSocketRequestModel: $webSocketRequestModel, responseStatus: $responseStatus, message: $message, httpResponseModel: $httpResponseModel, webSocketResponseModel: $webSocketResponseModel, isWorking: $isWorking, sendingTime: $sendingTime)'; } @override @@ -341,11 +411,15 @@ class _$RequestModelImpl implements _RequestModel { .equals(other.requestTabIndex, requestTabIndex) && (identical(other.httpRequestModel, httpRequestModel) || other.httpRequestModel == httpRequestModel) && + (identical(other.webSocketRequestModel, webSocketRequestModel) || + other.webSocketRequestModel == webSocketRequestModel) && (identical(other.responseStatus, responseStatus) || other.responseStatus == responseStatus) && (identical(other.message, message) || other.message == message) && (identical(other.httpResponseModel, httpResponseModel) || other.httpResponseModel == httpResponseModel) && + (identical(other.webSocketResponseModel, webSocketResponseModel) || + other.webSocketResponseModel == webSocketResponseModel) && (identical(other.isWorking, isWorking) || other.isWorking == isWorking) && (identical(other.sendingTime, sendingTime) || @@ -362,9 +436,11 @@ class _$RequestModelImpl implements _RequestModel { description, const DeepCollectionEquality().hash(requestTabIndex), httpRequestModel, + webSocketRequestModel, responseStatus, message, httpResponseModel, + webSocketResponseModel, isWorking, sendingTime); @@ -392,9 +468,11 @@ abstract class _RequestModel implements RequestModel { final String description, @JsonKey(includeToJson: false) final dynamic requestTabIndex, final HttpRequestModel? httpRequestModel, + final WebSocketRequestModel? webSocketRequestModel, final int? responseStatus, final String? message, final HttpResponseModel? httpResponseModel, + final WebSocketResponseModel? webSocketResponseModel, @JsonKey(includeToJson: false) final bool isWorking, @JsonKey(includeToJson: false) final DateTime? sendingTime}) = _$RequestModelImpl; @@ -416,12 +494,16 @@ abstract class _RequestModel implements RequestModel { @override HttpRequestModel? get httpRequestModel; @override + WebSocketRequestModel? get webSocketRequestModel; + @override int? get responseStatus; @override String? get message; @override HttpResponseModel? get httpResponseModel; @override + WebSocketResponseModel? get webSocketResponseModel; + @override @JsonKey(includeToJson: false) bool get isWorking; @override diff --git a/lib/models/request_model.g.dart b/lib/models/request_model.g.dart index 68b563953..4fe406112 100644 --- a/lib/models/request_model.g.dart +++ b/lib/models/request_model.g.dart @@ -17,12 +17,20 @@ _$RequestModelImpl _$$RequestModelImplFromJson(Map json) => _$RequestModelImpl( ? null : HttpRequestModel.fromJson( Map.from(json['httpRequestModel'] as Map)), + webSocketRequestModel: json['webSocketRequestModel'] == null + ? null + : WebSocketRequestModel.fromJson( + Map.from(json['webSocketRequestModel'] as Map)), responseStatus: (json['responseStatus'] as num?)?.toInt(), message: json['message'] as String?, httpResponseModel: json['httpResponseModel'] == null ? null : HttpResponseModel.fromJson( Map.from(json['httpResponseModel'] as Map)), + webSocketResponseModel: json['webSocketResponseModel'] == null + ? null + : WebSocketResponseModel.fromJson( + Map.from(json['webSocketResponseModel'] as Map)), isWorking: json['isWorking'] as bool? ?? false, sendingTime: json['sendingTime'] == null ? null @@ -36,12 +44,15 @@ Map _$$RequestModelImplToJson(_$RequestModelImpl instance) => 'name': instance.name, 'description': instance.description, 'httpRequestModel': instance.httpRequestModel?.toJson(), + 'webSocketRequestModel': instance.webSocketRequestModel?.toJson(), 'responseStatus': instance.responseStatus, 'message': instance.message, 'httpResponseModel': instance.httpResponseModel?.toJson(), + 'webSocketResponseModel': instance.webSocketResponseModel?.toJson(), }; const _$APITypeEnumMap = { APIType.rest: 'rest', APIType.graphql: 'graphql', + APIType.webSocket: 'webSocket', }; diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index 74bc2ff9c..3ae484b8c 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -17,6 +17,8 @@ class SettingsModel { this.historyRetentionPeriod = HistoryRetentionPeriod.oneWeek, this.workspaceFolderPath, this.isSSLDisabled = false, + this.pingInterval = 100, + this.isPinging = false, }); final bool isDark; @@ -31,6 +33,8 @@ class SettingsModel { final HistoryRetentionPeriod historyRetentionPeriod; final String? workspaceFolderPath; final bool isSSLDisabled; + final int? pingInterval; + final bool isPinging; SettingsModel copyWith({ bool? isDark, @@ -45,6 +49,8 @@ class SettingsModel { HistoryRetentionPeriod? historyRetentionPeriod, String? workspaceFolderPath, bool? isSSLDisabled, + int? pingInterval, + bool? isPinging, }) { return SettingsModel( isDark: isDark ?? this.isDark, @@ -61,6 +67,8 @@ class SettingsModel { historyRetentionPeriod ?? this.historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath, isSSLDisabled: isSSLDisabled ?? this.isSSLDisabled, + pingInterval: pingInterval ?? this.pingInterval, + isPinging: isPinging ?? this.isPinging, ); } @@ -80,6 +88,8 @@ class SettingsModel { historyRetentionPeriod: historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, + pingInterval: pingInterval, + isPinging: isPinging ); } @@ -134,6 +144,8 @@ class SettingsModel { } final workspaceFolderPath = data["workspaceFolderPath"] as String?; final isSSLDisabled = data["isSSLDisabled"] as bool?; + final pingInterval = data["pingInterval"] as int?; + final isPinging = data["isPinging"] as bool?; const sm = SettingsModel(); @@ -151,6 +163,8 @@ class SettingsModel { historyRetentionPeriod ?? HistoryRetentionPeriod.oneWeek, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, + pingInterval: pingInterval, + isPinging: isPinging ); } @@ -170,6 +184,8 @@ class SettingsModel { "historyRetentionPeriod": historyRetentionPeriod.name, "workspaceFolderPath": workspaceFolderPath, "isSSLDisabled": isSSLDisabled, + "pingInterval": pingInterval, + "isPinging": isPinging }; } @@ -194,7 +210,9 @@ class SettingsModel { other.activeEnvironmentId == activeEnvironmentId && other.historyRetentionPeriod == historyRetentionPeriod && other.workspaceFolderPath == workspaceFolderPath && - other.isSSLDisabled == isSSLDisabled; + other.isSSLDisabled == isSSLDisabled && + other.pingInterval == pingInterval && + other.isPinging == isPinging; } @override @@ -213,6 +231,8 @@ class SettingsModel { historyRetentionPeriod, workspaceFolderPath, isSSLDisabled, + pingInterval, + isPinging ); } } diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 35bc4aa0c..a0690a9ef 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -25,18 +25,23 @@ final requestSequenceProvider = StateProvider>((ref) { return ids ?? []; }); + +final WebSocketManager webSocketManager = WebSocketManager(); + final StateNotifierProvider?> collectionStateNotifierProvider = StateNotifierProvider((ref) => CollectionStateNotifier( ref, hiveHandler, - )); + webSocketManager + )); class CollectionStateNotifier extends StateNotifier?> { CollectionStateNotifier( this.ref, this.hiveHandler, + this.webSocketManager ) : super(null) { var status = loadData(); Future.microtask(() { @@ -53,7 +58,8 @@ class CollectionStateNotifier final Ref ref; final HiveHandler hiveHandler; final baseHttpResponseModel = const HttpResponseModel(); - + final WebSocketManager webSocketManager; + bool hasId(String id) => state?.keys.contains(id) ?? false; RequestModel? getRequestModel(String id) { @@ -69,6 +75,7 @@ class CollectionStateNotifier final newRequestModel = RequestModel( id: id, httpRequestModel: const HttpRequestModel(), + webSocketRequestModel: const WebSocketRequestModel(), ); var map = {...state!}; map[id] = newRequestModel; @@ -164,6 +171,7 @@ class CollectionStateNotifier responseStatus: null, message: null, httpResponseModel: null, + webSocketResponseModel: null, isWorking: false, sendingTime: null, ); @@ -222,47 +230,74 @@ class CollectionStateNotifier List? formData, int? responseStatus, String? message, + String? webSocketMessage, + ContentTypeWebSocket? contentType, HttpResponseModel? httpResponseModel, - }) { + WebSocketResponseModel? webSocketResponseModel, +}) { final rId = id ?? ref.read(selectedIdStateProvider); if (rId == null) { - debugPrint("Unable to update as Request Id is null"); - return; + debugPrint("Unable to update as Request Id is null"); + return; } + var currentModel = state![rId]!; + var currentApiType = apiType ?? currentModel.apiType; var currentHttpRequestModel = currentModel.httpRequestModel; + final newHttpRequestModel = currentApiType == APIType.rest || currentApiType ==APIType.graphql ? + currentHttpRequestModel?.copyWith( + method: method ?? currentHttpRequestModel.method, + url: url ?? currentHttpRequestModel.url, + headers: headers ?? currentHttpRequestModel.headers, + params: params ?? currentHttpRequestModel.params, + isHeaderEnabledList: isHeaderEnabledList ?? + currentHttpRequestModel.isHeaderEnabledList, + isParamEnabledList: + isParamEnabledList ?? currentHttpRequestModel.isParamEnabledList, + bodyContentType: + bodyContentType ?? currentHttpRequestModel.bodyContentType, + body: body ?? currentHttpRequestModel.body, + query: query ?? currentHttpRequestModel.query, + formData: formData ?? currentHttpRequestModel.formData, + ) : currentHttpRequestModel; + + var currentWebSocketRequestModel = currentModel.webSocketRequestModel ?? const WebSocketRequestModel(); + + final newWebSocketRequestModel = currentApiType == APIType.webSocket + ? currentWebSocketRequestModel.copyWith( + url: url ?? currentWebSocketRequestModel.url, + contentType: contentType ?? currentWebSocketRequestModel.contentType, + headers: headers ?? currentWebSocketRequestModel.headers, + params: params ?? currentWebSocketRequestModel.params, + isHeaderEnabledList: isHeaderEnabledList ?? + currentWebSocketRequestModel.isHeaderEnabledList, + isParamEnabledList: + isParamEnabledList ?? currentWebSocketRequestModel.isParamEnabledList, + message: webSocketMessage ?? currentWebSocketRequestModel.message, + ) + : currentWebSocketRequestModel; + final newModel = currentModel.copyWith( - apiType: apiType ?? currentModel.apiType, - name: name ?? currentModel.name, - description: description ?? currentModel.description, - requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex, - httpRequestModel: currentHttpRequestModel?.copyWith( - method: method ?? currentHttpRequestModel.method, - url: url ?? currentHttpRequestModel.url, - headers: headers ?? currentHttpRequestModel.headers, - params: params ?? currentHttpRequestModel.params, - isHeaderEnabledList: - isHeaderEnabledList ?? currentHttpRequestModel.isHeaderEnabledList, - isParamEnabledList: - isParamEnabledList ?? currentHttpRequestModel.isParamEnabledList, - bodyContentType: - bodyContentType ?? currentHttpRequestModel.bodyContentType, - body: body ?? currentHttpRequestModel.body, - query: query ?? currentHttpRequestModel.query, - formData: formData ?? currentHttpRequestModel.formData, - ), - responseStatus: responseStatus ?? currentModel.responseStatus, - message: message ?? currentModel.message, - httpResponseModel: httpResponseModel ?? currentModel.httpResponseModel, + apiType: apiType ?? currentModel.apiType, + name: name ?? currentModel.name, + description: description ?? currentModel.description, + requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex, + httpRequestModel: newHttpRequestModel, + webSocketRequestModel: newWebSocketRequestModel, + responseStatus: responseStatus ?? currentModel.responseStatus, + message: message ?? currentModel.message, + httpResponseModel: httpResponseModel ?? currentModel.httpResponseModel, ); var map = {...state!}; map[rId] = newModel; state = map; unsave(); - } +} + Future sendRequest() async { + final requestId = ref.read(selectedIdStateProvider); ref.read(codePaneVisibleStateProvider.notifier).state = false; final defaultUriScheme = ref.read(settingsProvider).defaultUriScheme; @@ -277,6 +312,7 @@ class CollectionStateNotifier } APIType apiType = requestModel!.apiType; + HttpRequestModel substitutedHttpRequestModel = getSubstitutedHttpRequestModel(requestModel.httpRequestModel!); @@ -331,6 +367,8 @@ class CollectionStateNotifier ), httpRequestModel: substitutedHttpRequestModel, httpResponseModel: httpResponseModel, + webSocketRequestModel: newRequestModel.webSocketRequestModel!, + webSocketResponseModel: newRequestModel.webSocketResponseModel ??const WebSocketResponseModel() //still working ); ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model); } @@ -427,4 +465,298 @@ class CollectionStateNotifier activeEnvId, ); } + + Future sendFrames() async { + final requestId = ref.read(selectedIdStateProvider); + ref.read(codePaneVisibleStateProvider.notifier).state = false; + + + if (requestId == null || state == null) { + return; + } + RequestModel? requestModel = state![requestId]; + var currentWebSocketRequestModel = requestModel!.webSocketRequestModel; + if (currentWebSocketRequestModel == null) { + return; + } + + + String message = currentWebSocketRequestModel.message ?? ''; + late (String?,DateTime?,String?) frame; + if(currentWebSocketRequestModel.contentType == ContentTypeWebSocket.text){ + frame = await webSocketManager.sendText(requestId,message); + }else if(currentWebSocketRequestModel.contentType == ContentTypeWebSocket.binary){ + frame = await webSocketManager.sendBinary(requestId,message); + } + + late WebSocketResponseModel newWebSocketResponseModel; + if(frame.$1 != null){ + newWebSocketResponseModel = requestModel.webSocketResponseModel!.copyWith( + frames: [ + ...?requestModel.webSocketResponseModel?.frames, + WebSocketFrameModel( + id: getNewUuid(), + message:frame.$1!, + timeStamp: frame.$2, + isSend: true + ), + ], + ); + + }else if(frame.$3 != null){ + newWebSocketResponseModel = requestModel.webSocketResponseModel!.copyWith( + frames: [ + ...?requestModel.webSocketResponseModel?.frames, + WebSocketFrameModel( + id: getNewUuid(), + message:frame.$3!, + timeStamp: null, + isSend: true + ), + ], + ); + + + } + + + + + final newRequestModel = requestModel.copyWith( + webSocketResponseModel: newWebSocketResponseModel, + ); + + var map = {...state!}; + map[requestId] = newRequestModel; + state = map; + + unsave(); + } + + + Future connect() async { + final requestId = ref.read(selectedIdStateProvider); + ref.read(codePaneVisibleStateProvider.notifier).state = false; + if (requestId == null || state == null) { + return; + } + + RequestModel? requestModel = state![requestId]; + + if (requestModel?.webSocketRequestModel == null) { + return; + } + + bool isPinging = ref.read(settingsProvider).isPinging; + + webSocketManager.createWebSocketClient(requestId); + if(isPinging){ + Duration durationPinging = Duration(milliseconds: ref.read(settingsProvider).pingInterval!); + webSocketManager.setPingInterval(requestId,durationPinging); + }else{ + webSocketManager.setPingInterval(requestId,null); + } + + + + final webSocketRequestModel = requestModel!.webSocketRequestModel!; + final url = webSocketRequestModel.url; + final headers = webSocketRequestModel.headers; + final params = webSocketRequestModel.params; + var map = {...state!}; + + map[requestId] = requestModel.copyWith( + isWorking: true, + sendingTime: DateTime.now(), + webSocketResponseModel: const WebSocketResponseModel(), + ); + + requestModel = map[requestId]; + + state = map; + (String?,DateTime?) result = await webSocketManager.connect(requestId,url,headers,params); + + + + if(result.$1 == kMsgConnected){ + map = {...state!}; + + map[requestId] = requestModel!.copyWith( + isWorking: false, + responseStatus: 101, + message: kResponseCodeReasons[101], + webSocketRequestModel: webSocketRequestModel.copyWith( + isConnected:true + ), + + ); + + + requestModel = map[requestId]; + state = map; + + webSocketManager.listen( + requestId, + (message) async{ + + map = {...state!}; + requestModel = map[requestId]; + + + WebSocketResponseModel webSocketResponseModel = requestModel!.webSocketResponseModel!; + + WebSocketResponseModel newWebSocketResponseModel = webSocketResponseModel.copyWith( + frames: [...webSocketResponseModel.frames, WebSocketFrameModel( + id: getNewUuid(), + message: message, + timeStamp:DateTime.now(), + isSend: false + )] + ); + var newRequestModel = requestModel!.copyWith( + isWorking: false, + responseStatus: 101, + webSocketResponseModel: newWebSocketResponseModel, + ); + + + + map[requestId] = newRequestModel; + + state = map; + + }, + onError: (error) async{ + var statusCode = error.statusCode; + map = {...state!}; + requestModel = map[requestId]; + WebSocketResponseModel webSocketResponseModel = requestModel!.webSocketResponseModel!; + WebSocketResponseModel newWebSocketResponseModel = webSocketResponseModel.copyWith( + frames: [...webSocketResponseModel.frames, WebSocketFrameModel( + id: getNewUuid(), + message: error.toString(), + timeStamp:DateTime.now(), + isSend: true + )] + ); + var newRequestModel = requestModel!.copyWith( + responseStatus: statusCode, + message: kResponseCodeReasons[statusCode], + webSocketResponseModel: newWebSocketResponseModel, + ); + map[requestId] = newRequestModel; + state = map; + + }, + onDone: () async{ + map = {...state!}; + requestModel = map[requestId]; + WebSocketRequestModel webSocketRequestModel = requestModel!.webSocketRequestModel!; + WebSocketRequestModel newWebSocketRequestModel = webSocketRequestModel.copyWith( + isConnected: false + ); + var newRequestModel = requestModel!.copyWith( + webSocketRequestModel: newWebSocketRequestModel, + ); + map[requestId] = newRequestModel; + state = map; + + + }, + cancelOnError: false, + ); + }else{ + map = {...state!}; + WebSocketResponseModel newWebSocketResponseModel = requestModel!.webSocketResponseModel!.copyWith( + frames: [...requestModel.webSocketResponseModel!.frames, WebSocketFrameModel( + id: getNewUuid(), + message: result.$1!, + timeStamp:DateTime.now(), + isSend: false + )]); + map[requestId] = requestModel.copyWith( + isWorking: false, + responseStatus: 1002, + message: kResponseCodeReasons[1002], + sendingTime: result.$2, + webSocketResponseModel: newWebSocketResponseModel, + ); + + state = map; + } + } + + + Future disconnect() async { + final requestId = ref.read(selectedIdStateProvider); + if (requestId == null || state == null) { + return; + } + + webSocketManager.disconnect(requestId); + + + + RequestModel? requestModel = state![requestId]; + WebSocketRequestModel webSocketRequestModel = requestModel!.webSocketRequestModel!; + WebSocketRequestModel newWebSocketRequestModel = webSocketRequestModel.copyWith( + isConnected: false + ); + + var newRequestModel = requestModel.copyWith( + webSocketRequestModel: newWebSocketRequestModel, + ); + + + var map = {...state!}; + map[requestId] = newRequestModel; + state = map; + + } + + + void deleteAllFrames(){ + final requestId = ref.read(selectedIdStateProvider); + if (requestId == null || state == null) { + return; + } + RequestModel? requestModel = state![requestId]; + WebSocketResponseModel webSocketResponseModel = requestModel!.webSocketResponseModel!; + WebSocketResponseModel newWebSocketResponseModel = webSocketResponseModel.copyWith( + frames: [], + ); + + var newRequestModel = requestModel.copyWith( + webSocketResponseModel: newWebSocketResponseModel, + ); + var map = {...state!}; + map[requestId] = newRequestModel; + state = map; + } + + void deleteFrame(String id){ + final requestId = ref.read(selectedIdStateProvider); + if (requestId == null || state == null) { + return; + } + RequestModel? requestModel = state![requestId]; + if (requestModel == null || state == null) { + return; + } + WebSocketResponseModel webSocketResponseModel = requestModel.webSocketResponseModel!; + List newFrames = requestModel.webSocketResponseModel!.frames.where((element) => element.id != id).toList(); + WebSocketResponseModel newWebSocketResponseModel = webSocketResponseModel.copyWith( + frames: newFrames, + ); + + var newRequestModel = requestModel.copyWith( + webSocketResponseModel: newWebSocketResponseModel, + ); + var map = {...state!}; + map[requestId] = newRequestModel; + state = map; + + } + + } diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index 6b64343aa..0847ed41d 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -33,6 +33,8 @@ class ThemeStateNotifier extends StateNotifier { HistoryRetentionPeriod? historyRetentionPeriod, String? workspaceFolderPath, bool? isSSLDisabled, + int? pingInterval, + bool? isPinging }) async { state = state.copyWith( isDark: isDark, @@ -47,6 +49,8 @@ class ThemeStateNotifier extends StateNotifier { historyRetentionPeriod: historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, + pingInterval: pingInterval, + isPinging: isPinging, ); await setSettingsToSharedPrefs(state); } diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index c679f2779..3cf79e9b4 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -1,3 +1,4 @@ +import 'package:apidash/widgets/dropdown_websocket_content_type.dart'; import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; @@ -23,8 +24,8 @@ class EditRequestBody extends ConsumerWidget { return Column( children: [ - (apiType == APIType.rest) - ? const SizedBox( + switch(apiType){ + APIType.rest => const SizedBox( height: kHeaderHeight, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -35,8 +36,41 @@ class EditRequestBody extends ConsumerWidget { DropdownButtonBodyContentType(), ], ), - ) - : kSizedBoxEmpty, + ), + APIType.webSocket => //dont forget to make it switch and put for rest + SizedBox( + height: kHeaderHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Padding( + padding:EdgeInsets.only(left:10), + child: SizedBox( + height: kHeaderHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Select Content Type:", + ), + DropdownButtonBodyContentWebSocketType(), + ]),), + ), + + + Padding( + padding:const EdgeInsets.only(right:10), + child: SendButton(isWorking: false, onTap: (){ + ref.read(collectionStateNotifierProvider.notifier).sendFrames(); + }), + + ), + ], + ), + ), + _=> kSizedBoxEmpty, + + }, switch (apiType) { APIType.rest => Expanded( child: switch (contentType) { @@ -89,6 +123,29 @@ class EditRequestBody extends ConsumerWidget { ), ), ), + APIType.webSocket => Expanded( + + child: Padding( + padding: kPt5o10, + child: Stack( + children: [ + TextFieldEditor( + key: Key("$selectedId-websocket-body"), + fieldKey: "$selectedId-websocket-body-editor", + initialValue: requestModel?.webSocketRequestModel?.message, + onChanged: (String value) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(webSocketMessage: value); + }, + hintText: kHintMessage, + ), + + ], + + ), + ), + ), _ => kSizedBoxEmpty, } ], @@ -116,3 +173,26 @@ class DropdownButtonBodyContentType extends ConsumerWidget { ); } } + +class DropdownButtonBodyContentWebSocketType extends ConsumerWidget { + const DropdownButtonBodyContentWebSocketType({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.watch(selectedIdStateProvider); + final requestBodyContentType = ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketRequestModel?.contentType)); + return DropdownButtonContentTypeWebSocket( + contentType: requestBodyContentType, + onChanged: (ContentTypeWebSocket? value) { + + // ref.read(collectionStateNotifierProvider.notifier).update( + // contentType: value, + // ); + + }, + ); + } +} diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart index ecb3f6dab..3347fd6db 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart @@ -43,23 +43,55 @@ class EditRequestHeadersState extends ConsumerState { final selectedId = ref.watch(selectedIdStateProvider); ref.watch(selectedRequestModelProvider .select((value) => value?.httpRequestModel?.headers?.length)); - var rH = ref.read(selectedRequestModelProvider)?.httpRequestModel?.headers; - bool isHeadersEmpty = rH == null || rH.isEmpty; - headerRows = isHeadersEmpty + ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketRequestModel?.headers?.length)); + var apiType = ref.watch(selectedRequestModelProvider + .select((value) => value?.apiType)); + late List? rH; + late bool isHeadersEmpty; + if (apiType == APIType.webSocket) { + rH = ref.read(selectedRequestModelProvider)?.webSocketRequestModel?.headers; + isHeadersEmpty = rH == null || rH.isEmpty; + isRowEnabledList = [ + ...(ref + .read(selectedRequestModelProvider) + ?.webSocketRequestModel + ?.isHeaderEnabledList ?? + List.filled(rH?.length ?? 0, true, growable: true)) + ]; + headerRows = isHeadersEmpty ? [ kNameValueEmptyModel, ] : rH + [kNameValueEmptyModel]; isRowEnabledList = [ + ...(ref + .read(selectedRequestModelProvider) + ?.webSocketRequestModel + ?.isHeaderEnabledList ?? + List.filled(rH?.length ?? 0, true, growable: true)) + ]; + + }else{ + rH = ref.read(selectedRequestModelProvider)?.httpRequestModel?.headers; + isHeadersEmpty = rH == null || rH.isEmpty; + headerRows = isHeadersEmpty + ? [ + kNameValueEmptyModel, + ] + : rH + [kNameValueEmptyModel]; + isRowEnabledList = [ ...(ref .read(selectedRequestModelProvider) ?.httpRequestModel ?.isHeaderEnabledList ?? List.filled(rH?.length ?? 0, true, growable: true)) ]; + + } isRowEnabledList.add(false); isAddingRow = false; - + List columns = const [ DataColumn2( label: Text(kNameCheckbox), @@ -84,7 +116,9 @@ class EditRequestHeadersState extends ConsumerState { List dataRows = List.generate( headerRows.length, (index) { + bool isLast = index + 1 == headerRows.length; + return DataRow( key: ValueKey("$selectedId-$index-headers-row-$seed"), cells: [ diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart index 9c852c71b..017fea243 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'request_pane_graphql.dart'; import 'request_pane_rest.dart'; +import 'request_pane_websocket.dart'; class EditRequestPane extends ConsumerWidget { const EditRequestPane({super.key}); @@ -17,6 +18,7 @@ class EditRequestPane extends ConsumerWidget { return switch (apiType) { APIType.rest => const EditRestRequestPane(), APIType.graphql => const EditGraphQLRequestPane(), + APIType.webSocket => const EditWebSocketRequestPane(), _ => kSizedBoxEmpty, }; } diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart index beae2b6c5..185f8885c 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart @@ -6,6 +6,7 @@ import 'package:apidash/widgets/widgets.dart'; import 'request_headers.dart'; import 'request_body.dart'; + class EditGraphQLRequestPane extends ConsumerWidget { const EditGraphQLRequestPane({super.key}); diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_websocket.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_websocket.dart new file mode 100644 index 000000000..adaae9d63 --- /dev/null +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_websocket.dart @@ -0,0 +1,60 @@ +import 'package:apidash/consts.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'request_headers.dart'; +import 'request_body.dart'; +import 'request_params.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; + +class EditWebSocketRequestPane extends ConsumerWidget { + const EditWebSocketRequestPane({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedId = ref.watch(selectedIdStateProvider); + var tabIndex = ref.watch( + selectedRequestModelProvider.select((value) => value?.requestTabIndex)); + final codePaneVisible = ref.watch(codePaneVisibleStateProvider); + final headerLength = ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketRequestModel?.headersMap.length)) ?? + 0; + final paramLength = ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketRequestModel?.paramsMap.length)) ?? + 0; + final message = ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketRequestModel?.message)) ?? + ""; + + return RequestPane( + selectedId: selectedId, + codePaneVisible: codePaneVisible, + tabIndex: tabIndex, + onPressedCodeButton: () { + ref.read(codePaneVisibleStateProvider.notifier).state = + !codePaneVisible; + }, + onTapTabBar: (index) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(requestTabIndex: index); + }, + showIndicators: [ + paramLength > 0, + !kIsWeb && headerLength > 0, + message.isNotEmpty, + ], + tabLabels: const [ + kLabelURLParams, + if(!kIsWeb) kLabelHeaders, + kLabelMessage, + ], + children: const [ + EditRequestURLParams(), + if (!kIsWeb) EditRequestHeaders(), + EditRequestBody(), + ], + ); + } +} diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_params.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_params.dart index a583b1839..63c0b4a86 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_params.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_params.dart @@ -42,20 +42,41 @@ class EditRequestURLParamsState extends ConsumerState { final selectedId = ref.watch(selectedIdStateProvider); ref.watch(selectedRequestModelProvider .select((value) => value?.httpRequestModel?.params?.length)); - var rP = ref.read(selectedRequestModelProvider)?.httpRequestModel?.params; - bool isParamsEmpty = rP == null || rP.isEmpty; - paramRows = isParamsEmpty - ? [ - kNameValueEmptyModel, - ] - : rP + [kNameValueEmptyModel]; - isRowEnabledList = [ + ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketRequestModel?.params?.length)); + var apiType = ref.watch(selectedRequestModelProvider + .select((value) => value?.apiType)); + late List? rP; + + + if(apiType == APIType.webSocket){ + rP= ref.read(selectedRequestModelProvider)?.webSocketRequestModel?.params; + isRowEnabledList = [ + ...(ref + .read(selectedRequestModelProvider) + ?.webSocketRequestModel + ?.isParamEnabledList ?? + List.filled(rP?.length ?? 0, true, growable: true)) + ]; + + }else{ + rP= ref.read(selectedRequestModelProvider)?.httpRequestModel?.params; + isRowEnabledList = [ ...(ref .read(selectedRequestModelProvider) ?.httpRequestModel ?.isParamEnabledList ?? List.filled(rP?.length ?? 0, true, growable: true)) ]; + } + bool isParamsEmpty = rP == null || rP.isEmpty; + paramRows = isParamsEmpty + ? [ + kNameValueEmptyModel, + ] + : rP + [kNameValueEmptyModel]; + + isRowEnabledList.add(false); isAddingRow = false; diff --git a/lib/screens/home_page/editor_pane/details_card/response_pane.dart b/lib/screens/home_page/editor_pane/details_card/response_pane.dart index 50d5531a0..37160bed4 100644 --- a/lib/screens/home_page/editor_pane/details_card/response_pane.dart +++ b/lib/screens/home_page/editor_pane/details_card/response_pane.dart @@ -5,7 +5,7 @@ import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; -class ResponsePane extends ConsumerWidget { +class ResponsePane extends ConsumerWidget{ const ResponsePane({super.key}); @override @@ -19,12 +19,16 @@ class ResponsePane extends ConsumerWidget { selectedRequestModelProvider.select((value) => value?.responseStatus)); final message = ref .watch(selectedRequestModelProvider.select((value) => value?.message)); + + if (isWorking) { return SendingWidget( startSendingTime: startSendingTime, ); + } + if (responseStatus == null) { return const NotSentWidget(); } @@ -78,11 +82,20 @@ class ResponseTabs extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedId = ref.watch(selectedIdStateProvider); + final apiType = ref + .watch(selectedRequestModelProvider.select((value) => value?.apiType)); + return ResponseTabView( selectedId: selectedId, - children: const [ - ResponseBodyTab(), - ResponseHeadersTab(), + children: [ + if (apiType == APIType.rest || apiType == APIType.graphql) ...const [ + ResponseBodyTab(), + ResponseHeadersTab(), + ] else if (apiType == APIType.webSocket) ...const [ + WebsocketResponseView(), + ResponseHeadersTab(), + ], + ], ); } @@ -105,15 +118,98 @@ class ResponseHeadersTab extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final requestHeaders = ref.watch(selectedRequestModelProvider + final requestHttpHeaders = ref.watch(selectedRequestModelProvider .select((value) => value?.httpResponseModel?.requestHeaders)) ?? {}; - final responseHeaders = ref.watch(selectedRequestModelProvider + final responseHttpHeaders = ref.watch(selectedRequestModelProvider .select((value) => value?.httpResponseModel?.headers)) ?? {}; - return ResponseHeaders( - responseHeaders: responseHeaders, - requestHeaders: requestHeaders, + final requestWebSocketHeaders = ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketResponseModel?.requestHeaders)) ?? + {}; + final responseWebSocketHeaders = ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketResponseModel?.headers)) ?? + {}; + final apiType = ref + .watch(selectedRequestModelProvider.select((value) => value?.apiType)); + switch (apiType!) { + case APIType.rest || APIType.graphql: + return ResponseHeaders( + responseHeaders: responseHttpHeaders, + requestHeaders: requestHttpHeaders, + ); + case APIType.webSocket: + return ResponseHeaders( + responseHeaders: responseWebSocketHeaders, + requestHeaders: requestWebSocketHeaders, + ); + } + + } +} + + + +class WebsocketResponseView extends ConsumerStatefulWidget { + const WebsocketResponseView({super.key}); + + @override + ConsumerState createState() => _WebsocketResponseViewState(); +} + +class _WebsocketResponseViewState extends ConsumerState { + final ScrollController _controller = ScrollController(); + bool _isAtTop = true; + List _pausedFrames = []; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_controller.hasClients) { + _controller.animateTo( + _controller.position.minScrollExtent, + duration: const Duration(milliseconds: 500), // Adjust for speed + curve: Curves.easeOut, // Smooth effect + ); + } + }); + _controller.addListener(() { + if (_controller.hasClients) { + setState(() { + _isAtTop = _controller.position.atEdge == true; + }); + } + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + + final frames = ref.watch(selectedRequestModelProvider + .select((value) => value?.webSocketResponseModel?.frames)) ?? + []; + + if (_isAtTop) { + _pausedFrames = List.from(frames); + } + + final displayFrames = _isAtTop ? frames : _pausedFrames; + return ListView.builder( + controller: _controller, + itemCount: displayFrames.length, + itemBuilder: (context, index) { + return WebsocketFrame( + websocketFrame: displayFrames[displayFrames.length-index-1], + ref: ref, + ); + }, ); } } diff --git a/lib/screens/home_page/editor_pane/url_card.dart b/lib/screens/home_page/editor_pane/url_card.dart index 829bc5c97..97c7ffaa8 100644 --- a/lib/screens/home_page/editor_pane/url_card.dart +++ b/lib/screens/home_page/editor_pane/url_card.dart @@ -35,6 +35,7 @@ class EditorPaneRequestURLCard extends ConsumerWidget { switch (apiType) { APIType.rest => const DropdownButtonHTTPMethod(), APIType.graphql => kSizedBoxEmpty, + APIType.webSocket => kSizedBoxEmpty, null => kSizedBoxEmpty, }, switch (apiType) { @@ -51,6 +52,7 @@ class EditorPaneRequestURLCard extends ConsumerWidget { switch (apiType) { APIType.rest => const DropdownButtonHTTPMethod(), APIType.graphql => kSizedBoxEmpty, + APIType.webSocket => kSizedBoxEmpty, null => kSizedBoxEmpty, }, switch (apiType) { @@ -61,10 +63,11 @@ class EditorPaneRequestURLCard extends ConsumerWidget { child: URLTextField(), ), kHSpacer20, - const SizedBox( - height: 36, - child: SendRequestButton(), - ) + switch (apiType) { + APIType.rest || APIType.graphql => const SendRequestButton(), + APIType.webSocket =>const ConnectionRequestButton(), + null => kSizedBoxEmpty, + }, ], ), ), @@ -100,13 +103,23 @@ class URLTextField extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedId = ref.watch(selectedIdStateProvider); + final apiType = ref + .watch(selectedRequestModelProvider.select((value) => value?.apiType)); return EnvURLField( selectedId: selectedId!, - initialValue: ref - .read(collectionStateNotifierProvider.notifier) + initialValue:switch(apiType){ + APIType.rest || APIType.graphql => ref + .read(collectionStateNotifierProvider.notifier) + .getRequestModel(selectedId) + ?.httpRequestModel + ?.url, + APIType.webSocket => ref + .read(collectionStateNotifierProvider.notifier) .getRequestModel(selectedId) - ?.httpRequestModel + ?.webSocketRequestModel ?.url, + _=>"" + }, onChanged: (value) { ref.read(collectionStateNotifierProvider.notifier).update(url: value); }, @@ -117,6 +130,8 @@ class URLTextField extends ConsumerWidget { } } + + class SendRequestButton extends ConsumerWidget { final Function()? onTap; const SendRequestButton({ @@ -129,7 +144,6 @@ class SendRequestButton extends ConsumerWidget { ref.watch(selectedIdStateProvider); final isWorking = ref.watch( selectedRequestModelProvider.select((value) => value?.isWorking)); - return SendButton( isWorking: isWorking ?? false, onTap: () { @@ -142,3 +156,31 @@ class SendRequestButton extends ConsumerWidget { ); } } + + +class ConnectionRequestButton extends ConsumerWidget { + final Function()? onTap; + const ConnectionRequestButton({ + super.key, + this.onTap, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.watch(selectedIdStateProvider); + final isConnected = ref.watch( + selectedRequestModelProvider.select((value) => value?.webSocketRequestModel!.isConnected)); + return ConnectionButton( + isConnected:isConnected?? false, + onTap: () { + onTap?.call(); + ref.read(collectionStateNotifierProvider.notifier).connect(); + }, + onDisconnect: () { + onTap?.call(); + ref.read(collectionStateNotifierProvider.notifier).disconnect(); + + }, + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index eb4b60088..ce956f053 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -97,7 +97,7 @@ class SettingsPage extends ConsumerWidget { trailing: CodegenPopupMenu( value: settings.defaultCodeGenLang, onChanged: (value) { - ref + ref .read(settingsProvider.notifier) .update(defaultCodeGenLang: value); }, @@ -224,6 +224,44 @@ class SettingsPage extends ConsumerWidget { ), ), ), + !kIsWeb + ? SwitchListTile( + hoverColor: kColorTransparent, + title: const Text('Enable Pinging'), + subtitle: Text( + 'Current selection: ${settings.isPinging ? "Pinging Enabled" : "Pinging Disabled"}', + + ), + value: settings.isPinging, + onChanged: (bool value) { + ref + .read(settingsProvider.notifier) + .update(isPinging: value ); + }, + ) + : kSizedBoxEmpty, + !kIsWeb + ? + ListTile( + hoverColor: kColorTransparent, + title: const Text('Interval Between Ping Requests'), + subtitle: Text( + 'Current interval between consecutive pings are: ${settings.pingInterval} milliseconds'), + trailing: SizedBox( + width: 120, + child: TextField( + decoration: InputDecoration( + hintText: settings.pingInterval.toString() + ), + keyboardType: TextInputType.number, + onChanged: (value) { + ref + .read(settingsProvider.notifier) + .update(pingInterval: int.parse(value)); + }, + ), + ), + ) : kSizedBoxEmpty, ListTile( title: const Text('About'), subtitle: const Text( diff --git a/lib/utils/ui_utils.dart b/lib/utils/ui_utils.dart index 4f72a1a3f..134edf55d 100644 --- a/lib/utils/ui_utils.dart +++ b/lib/utils/ui_utils.dart @@ -36,6 +36,7 @@ Color getAPIColor( method, ), APIType.graphql => kColorGQL, + APIType.webSocket => kColorWS, }; if (brightness == Brightness.dark) { col = getDarkModeColor(col); diff --git a/lib/widgets/button_connection.dart b/lib/widgets/button_connection.dart new file mode 100644 index 000000000..6329abd97 --- /dev/null +++ b/lib/widgets/button_connection.dart @@ -0,0 +1,44 @@ +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:apidash/consts.dart'; + +class ConnectionButton extends StatelessWidget { + const ConnectionButton({ + super.key, + required this.isConnected, + required this.onTap, + this.onDisconnect, + }); + + final bool isConnected; + final void Function() onTap; + final void Function()? onDisconnect; + + @override + Widget build(BuildContext context) { + return ADFilledButton( + onPressed: isConnected ? onDisconnect : onTap, + isTonal: isConnected ? true : false, + items: isConnected + ? const [ + kHSpacer8, + Text( + kLabelDisconnect, + style: kTextStyleButton, + ), + kHSpacer6, + ] + : const [ + Text( + kLabelConnect, + style: kTextStyleButton, + ), + kHSpacer10, + Icon( + size: 16, + Icons.send, + ), + ], + ); + } +} diff --git a/lib/widgets/dropdown_websocket_content_type.dart b/lib/widgets/dropdown_websocket_content_type.dart new file mode 100644 index 000000000..990471e6b --- /dev/null +++ b/lib/widgets/dropdown_websocket_content_type.dart @@ -0,0 +1,24 @@ +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; + +class DropdownButtonContentTypeWebSocket extends StatelessWidget { + const DropdownButtonContentTypeWebSocket({ + super.key, + this.contentType, + this.onChanged, + }); + + final ContentTypeWebSocket? contentType; + final void Function(ContentTypeWebSocket?)? onChanged; + + @override + Widget build(BuildContext context) { + return ADDropdownButton( + value: contentType, + values: ContentTypeWebSocket.values.map((e) => (e, e.name)), + onChanged: onChanged, + iconSize: 16, + ); + } +} diff --git a/lib/widgets/request_pane.dart b/lib/widgets/request_pane.dart index 95871d532..248291c1e 100644 --- a/lib/widgets/request_pane.dart +++ b/lib/widgets/request_pane.dart @@ -34,8 +34,10 @@ class RequestPane extends StatefulHookWidget { class _RequestPaneState extends State with TickerProviderStateMixin { + @override Widget build(BuildContext context) { + final TabController controller = useTabController( initialLength: widget.children.length, vsync: this, diff --git a/lib/widgets/texts.dart b/lib/widgets/texts.dart index c64a71b27..97acf1e7e 100644 --- a/lib/widgets/texts.dart +++ b/lib/widgets/texts.dart @@ -20,6 +20,7 @@ class SidebarRequestCardTextBox extends StatelessWidget { switch (apiType) { APIType.rest => method.abbr, APIType.graphql => apiType.abbr, + APIType.webSocket => apiType.abbr, }, textAlign: TextAlign.center, style: TextStyle( diff --git a/lib/widgets/websocket_frame.dart b/lib/widgets/websocket_frame.dart new file mode 100644 index 000000000..7ddf6ef2f --- /dev/null +++ b/lib/widgets/websocket_frame.dart @@ -0,0 +1,144 @@ +import 'package:apidash/providers/collection_providers.dart'; + +import 'package:apidash_core/models/websocket_frame_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + + +class WebsocketFrame extends StatefulWidget { + final WebSocketFrameModel websocketFrame; + final WidgetRef ref; + const WebsocketFrame({super.key, required this.websocketFrame,required this.ref}); + + @override + State createState() => _WebsocketFrameState(); +} + +class _WebsocketFrameState extends State { + bool _isExpanded = false; + + void _toggleExpand() { + setState(() { + _isExpanded = !_isExpanded; + }); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _toggleExpand, + child: Container( + margin:const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.white10), + borderRadius: BorderRadius.circular(8.0), + ), + child: ListTile( + contentPadding:const EdgeInsets.symmetric(horizontal: 8.0), + leading: Icon( + widget.websocketFrame.isSend ? Icons.arrow_upward :Icons.arrow_downward, + ), + title: Text( + widget.websocketFrame.message, + maxLines: _isExpanded ? null : 1, + overflow: _isExpanded ? TextOverflow.visible : TextOverflow.ellipsis, + ), + subtitle: Icon( + _isExpanded ? Icons.expand_less : Icons.expand_more, + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(widget.websocketFrame.formattedTime), + IconButton(onPressed: () { + widget.ref.read(collectionStateNotifierProvider.notifier).deleteFrame(widget.websocketFrame.id); + }, icon: const Icon(Icons.delete)), + ], + ), + ), + ), + ); + } +} + +// import 'package:apidash/providers/collection_providers.dart'; +// import 'package:apidash_core/models/websocket_frame_model.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; + +// class WebsocketFrame extends ConsumerWidget { +// final WebSocketFrameModel websocketFrame; + +// const WebsocketFrame({super.key, required this.websocketFrame}); + +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// bool _isExpanded = false; + +// void _toggleExpand() { + +// _isExpanded = !_isExpanded; +// } + + +// return GestureDetector( +// onTap: _toggleExpand, +// child: AnimatedContainer( +// duration: const Duration(milliseconds: 300), +// margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), +// padding: const EdgeInsets.all(8.0), +// decoration: BoxDecoration( +// color: _isExpanded ? Colors.grey.shade900 : Colors.grey.shade800, +// border: Border.all(color: Colors.white10), +// borderRadius: BorderRadius.circular(8.0), +// ), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// ListTile( +// contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), +// leading: Icon( +// websocketFrame.isSend ? Icons.arrow_upward : Icons.arrow_downward, +// color: websocketFrame.isSend ? Colors.green : Colors.red, +// ), +// title: Text( +// websocketFrame.message, +// maxLines: _isExpanded ? null : 1, +// overflow: _isExpanded ? TextOverflow.visible : TextOverflow.ellipsis, +// style: const TextStyle(color: Colors.white), +// ), +// subtitle: Row( +// children: [ +// Icon( +// _isExpanded ? Icons.expand_less : Icons.expand_more, +// color: Colors.white70, +// ), +// const SizedBox(width: 4), +// Text( +// _isExpanded ? "Collapse" : "Expand", +// style: const TextStyle(color: Colors.white70, fontSize: 12), +// ), +// ], +// ), +// trailing: Row( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Text( +// websocketFrame.formattedTime, +// style: const TextStyle(color: Colors.white54, fontSize: 12), +// ), +// IconButton( +// onPressed: () { +// ref.read(collectionStateNotifierProvider.notifier).deleteFrame(websocketFrame.id); +// }, +// icon: const Icon(Icons.delete, color: Colors.redAccent), +// ), +// ], +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index f64276042..cb622f30b 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -61,3 +61,5 @@ export 'texts.dart'; export 'uint8_audio_player.dart'; export 'window_caption.dart'; export 'workspace_selector.dart'; +export 'button_connection.dart'; +export 'websocket_frame.dart'; \ No newline at end of file diff --git a/packages/apidash_core/lib/consts.dart b/packages/apidash_core/lib/consts.dart index 1d7044c74..04e138b08 100644 --- a/packages/apidash_core/lib/consts.dart +++ b/packages/apidash_core/lib/consts.dart @@ -2,7 +2,8 @@ import 'dart:convert'; enum APIType { rest("HTTP", "HTTP"), - graphql("GraphQL", "GQL"); + graphql("GraphQL", "GQL"), + webSocket("WebSocket", "WS"); const APIType(this.label, this.abbr); final String label; @@ -23,7 +24,7 @@ enum HTTPVerb { final String abbr; } -enum SupportedUriSchemes { https, http } +enum SupportedUriSchemes { https, http , ws, wss} final kSupportedUriSchemes = SupportedUriSchemes.values.map((i) => i.name).toList(); @@ -76,6 +77,8 @@ const kSubTypeFormData = "form-data"; const kSubTypeDefaultViewOptions = 'all'; +const kTypeBinary = 'binary'; + enum ContentType { json("$kTypeApplication/$kSubTypeJson"), text("$kTypeText/$kSubTypePlain"), @@ -85,6 +88,16 @@ enum ContentType { final String header; } +enum ContentTypeWebSocket { + + text(kTypeText), + binary(kTypeBinary); + + + const ContentTypeWebSocket(this.header); + final String header; +} + const JsonEncoder kJsonEncoder = JsonEncoder.withIndent(' '); const JsonDecoder kJsonDecoder = JsonDecoder(); const LineSplitter kSplitter = LineSplitter(); @@ -93,3 +106,5 @@ const kCodeCharsPerLineLimit = 200; const kHeaderContentType = "Content-Type"; const kMsgRequestCancelled = 'Request Cancelled'; +const kMsgConnected = 'Connected'; +const kMsgDisconnected = 'Disconnected'; diff --git a/packages/apidash_core/lib/models/models.dart b/packages/apidash_core/lib/models/models.dart index c50ec988f..a2e26935a 100644 --- a/packages/apidash_core/lib/models/models.dart +++ b/packages/apidash_core/lib/models/models.dart @@ -1,3 +1,6 @@ export 'environment_model.dart'; export 'http_request_model.dart'; export 'http_response_model.dart'; +export 'websocket_request_model.dart'; +export 'websocket_response_model.dart'; +export 'websocket_frame_model.dart'; diff --git a/packages/apidash_core/lib/models/websocket_frame_model.dart b/packages/apidash_core/lib/models/websocket_frame_model.dart new file mode 100644 index 000000000..ea6c6e413 --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_frame_model.dart @@ -0,0 +1,33 @@ +import 'dart:typed_data'; +import 'package:apidash_core/models/http_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:intl/intl.dart'; +part 'websocket_frame_model.freezed.dart'; +part 'websocket_frame_model.g.dart'; + + +@freezed +class WebSocketFrameModel with _$WebSocketFrameModel { + const WebSocketFrameModel._(); + + @JsonSerializable(explicitToJson: true, anyMap: true) + const factory WebSocketFrameModel({ + required String id, + @Default("") String frameType, + @Default("") String message, + @Uint8ListConverter() Uint8List? binaryData, + Map? metadata, + @Default(false) bool isSend, + DateTime? timeStamp, + }) = _WebSocketFrameModel; + + factory WebSocketFrameModel.fromJson(Map json) => + _$WebSocketFrameModelFromJson(json); + + String get formattedTime => DateFormat('HH:mm:ss.SSS').format(timeStamp ?? DateTime.now()); + bool get isTextFrame => frameType.toLowerCase() == "text"; + + + bool get isBinaryFrame => frameType.toLowerCase() == "binary"; + +} diff --git a/packages/apidash_core/lib/models/websocket_frame_model.freezed.dart b/packages/apidash_core/lib/models/websocket_frame_model.freezed.dart new file mode 100644 index 000000000..2efd56a2d --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_frame_model.freezed.dart @@ -0,0 +1,318 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'websocket_frame_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +WebSocketFrameModel _$WebSocketFrameModelFromJson(Map json) { + return _WebSocketFrameModel.fromJson(json); +} + +/// @nodoc +mixin _$WebSocketFrameModel { + String get id => throw _privateConstructorUsedError; + String get frameType => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + @Uint8ListConverter() + Uint8List? get binaryData => throw _privateConstructorUsedError; + Map? get metadata => throw _privateConstructorUsedError; + bool get isSend => throw _privateConstructorUsedError; + DateTime? get timeStamp => throw _privateConstructorUsedError; + + /// Serializes this WebSocketFrameModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of WebSocketFrameModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $WebSocketFrameModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WebSocketFrameModelCopyWith<$Res> { + factory $WebSocketFrameModelCopyWith( + WebSocketFrameModel value, $Res Function(WebSocketFrameModel) then) = + _$WebSocketFrameModelCopyWithImpl<$Res, WebSocketFrameModel>; + @useResult + $Res call( + {String id, + String frameType, + String message, + @Uint8ListConverter() Uint8List? binaryData, + Map? metadata, + bool isSend, + DateTime? timeStamp}); +} + +/// @nodoc +class _$WebSocketFrameModelCopyWithImpl<$Res, $Val extends WebSocketFrameModel> + implements $WebSocketFrameModelCopyWith<$Res> { + _$WebSocketFrameModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of WebSocketFrameModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? frameType = null, + Object? message = null, + Object? binaryData = freezed, + Object? metadata = freezed, + Object? isSend = null, + Object? timeStamp = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + frameType: null == frameType + ? _value.frameType + : frameType // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + binaryData: freezed == binaryData + ? _value.binaryData + : binaryData // ignore: cast_nullable_to_non_nullable + as Uint8List?, + metadata: freezed == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + isSend: null == isSend + ? _value.isSend + : isSend // ignore: cast_nullable_to_non_nullable + as bool, + timeStamp: freezed == timeStamp + ? _value.timeStamp + : timeStamp // ignore: cast_nullable_to_non_nullable + as DateTime?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WebSocketFrameModelImplCopyWith<$Res> + implements $WebSocketFrameModelCopyWith<$Res> { + factory _$$WebSocketFrameModelImplCopyWith(_$WebSocketFrameModelImpl value, + $Res Function(_$WebSocketFrameModelImpl) then) = + __$$WebSocketFrameModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String frameType, + String message, + @Uint8ListConverter() Uint8List? binaryData, + Map? metadata, + bool isSend, + DateTime? timeStamp}); +} + +/// @nodoc +class __$$WebSocketFrameModelImplCopyWithImpl<$Res> + extends _$WebSocketFrameModelCopyWithImpl<$Res, _$WebSocketFrameModelImpl> + implements _$$WebSocketFrameModelImplCopyWith<$Res> { + __$$WebSocketFrameModelImplCopyWithImpl(_$WebSocketFrameModelImpl _value, + $Res Function(_$WebSocketFrameModelImpl) _then) + : super(_value, _then); + + /// Create a copy of WebSocketFrameModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? frameType = null, + Object? message = null, + Object? binaryData = freezed, + Object? metadata = freezed, + Object? isSend = null, + Object? timeStamp = freezed, + }) { + return _then(_$WebSocketFrameModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + frameType: null == frameType + ? _value.frameType + : frameType // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + binaryData: freezed == binaryData + ? _value.binaryData + : binaryData // ignore: cast_nullable_to_non_nullable + as Uint8List?, + metadata: freezed == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + isSend: null == isSend + ? _value.isSend + : isSend // ignore: cast_nullable_to_non_nullable + as bool, + timeStamp: freezed == timeStamp + ? _value.timeStamp + : timeStamp // ignore: cast_nullable_to_non_nullable + as DateTime?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true) +class _$WebSocketFrameModelImpl extends _WebSocketFrameModel { + const _$WebSocketFrameModelImpl( + {required this.id, + this.frameType = "", + this.message = "", + @Uint8ListConverter() this.binaryData, + final Map? metadata, + this.isSend = false, + this.timeStamp}) + : _metadata = metadata, + super._(); + + factory _$WebSocketFrameModelImpl.fromJson(Map json) => + _$$WebSocketFrameModelImplFromJson(json); + + @override + final String id; + @override + @JsonKey() + final String frameType; + @override + @JsonKey() + final String message; + @override + @Uint8ListConverter() + final Uint8List? binaryData; + final Map? _metadata; + @override + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey() + final bool isSend; + @override + final DateTime? timeStamp; + + @override + String toString() { + return 'WebSocketFrameModel(id: $id, frameType: $frameType, message: $message, binaryData: $binaryData, metadata: $metadata, isSend: $isSend, timeStamp: $timeStamp)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WebSocketFrameModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.frameType, frameType) || + other.frameType == frameType) && + (identical(other.message, message) || other.message == message) && + const DeepCollectionEquality() + .equals(other.binaryData, binaryData) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.isSend, isSend) || other.isSend == isSend) && + (identical(other.timeStamp, timeStamp) || + other.timeStamp == timeStamp)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + frameType, + message, + const DeepCollectionEquality().hash(binaryData), + const DeepCollectionEquality().hash(_metadata), + isSend, + timeStamp); + + /// Create a copy of WebSocketFrameModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WebSocketFrameModelImplCopyWith<_$WebSocketFrameModelImpl> get copyWith => + __$$WebSocketFrameModelImplCopyWithImpl<_$WebSocketFrameModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$WebSocketFrameModelImplToJson( + this, + ); + } +} + +abstract class _WebSocketFrameModel extends WebSocketFrameModel { + const factory _WebSocketFrameModel( + {required final String id, + final String frameType, + final String message, + @Uint8ListConverter() final Uint8List? binaryData, + final Map? metadata, + final bool isSend, + final DateTime? timeStamp}) = _$WebSocketFrameModelImpl; + const _WebSocketFrameModel._() : super._(); + + factory _WebSocketFrameModel.fromJson(Map json) = + _$WebSocketFrameModelImpl.fromJson; + + @override + String get id; + @override + String get frameType; + @override + String get message; + @override + @Uint8ListConverter() + Uint8List? get binaryData; + @override + Map? get metadata; + @override + bool get isSend; + @override + DateTime? get timeStamp; + + /// Create a copy of WebSocketFrameModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WebSocketFrameModelImplCopyWith<_$WebSocketFrameModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/apidash_core/lib/models/websocket_frame_model.g.dart b/packages/apidash_core/lib/models/websocket_frame_model.g.dart new file mode 100644 index 000000000..cbbfc8100 --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_frame_model.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'websocket_frame_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$WebSocketFrameModelImpl _$$WebSocketFrameModelImplFromJson(Map json) => + _$WebSocketFrameModelImpl( + id: json['id'] as String, + frameType: json['frameType'] as String? ?? "", + message: json['message'] as String? ?? "", + binaryData: + const Uint8ListConverter().fromJson(json['binaryData'] as List?), + metadata: (json['metadata'] as Map?)?.map( + (k, e) => MapEntry(k as String, e as String), + ), + isSend: json['isSend'] as bool? ?? false, + timeStamp: json['timeStamp'] == null + ? null + : DateTime.parse(json['timeStamp'] as String), + ); + +Map _$$WebSocketFrameModelImplToJson( + _$WebSocketFrameModelImpl instance) => + { + 'id': instance.id, + 'frameType': instance.frameType, + 'message': instance.message, + 'binaryData': const Uint8ListConverter().toJson(instance.binaryData), + 'metadata': instance.metadata, + 'isSend': instance.isSend, + 'timeStamp': instance.timeStamp?.toIso8601String(), + }; diff --git a/packages/apidash_core/lib/models/websocket_request_model.dart b/packages/apidash_core/lib/models/websocket_request_model.dart new file mode 100644 index 000000000..346479721 --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_request_model.dart @@ -0,0 +1,51 @@ +import 'package:apidash_core/consts.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:seed/models/name_value_model.dart'; +import '../utils/utils.dart' + show rowsToMap, getEnabledRows; +part 'websocket_request_model.freezed.dart'; +part 'websocket_request_model.g.dart'; + +@freezed +class WebSocketRequestModel with _$WebSocketRequestModel { + const WebSocketRequestModel._(); + + @JsonSerializable( + explicitToJson: true, + anyMap: true, + ) + const factory WebSocketRequestModel({ + @Default("") String url, + @Default(ContentTypeWebSocket.text) ContentTypeWebSocket contentType, + bool? isConnected, + List? headers, + List? isHeaderEnabledList, + List? params, + List? isParamEnabledList, + String? message, + }) = _WebSocketRequestModel; + + factory WebSocketRequestModel.fromJson(Map json) => + _$WebSocketRequestModelFromJson(json); + + + Map get headersMap => rowsToMap(headers) ?? {}; + List? get enabledHeaders => + getEnabledRows(headers, isHeaderEnabledList); + Map get enabledHeadersMap => rowsToMap(enabledHeaders) ?? {}; + bool get hasHeaders => enabledHeadersMap.isNotEmpty; + + + Map get paramsMap => rowsToMap(params) ?? {}; + List? get enabledParams => + getEnabledRows(params, isParamEnabledList); + Map get enabledParamsMap => rowsToMap(enabledParams) ?? {}; + bool get hasParams => enabledParamsMap.isNotEmpty; + + + bool get isValidUrl => url.startsWith("ws://") || url.startsWith("wss://"); + + List resetReceivedMessages() { + return []; + } +} diff --git a/packages/apidash_core/lib/models/websocket_request_model.freezed.dart b/packages/apidash_core/lib/models/websocket_request_model.freezed.dart new file mode 100644 index 000000000..26a23b27a --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_request_model.freezed.dart @@ -0,0 +1,369 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'websocket_request_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +WebSocketRequestModel _$WebSocketRequestModelFromJson( + Map json) { + return _WebSocketRequestModel.fromJson(json); +} + +/// @nodoc +mixin _$WebSocketRequestModel { + String get url => throw _privateConstructorUsedError; + ContentTypeWebSocket get contentType => throw _privateConstructorUsedError; + bool? get isConnected => throw _privateConstructorUsedError; + List? get headers => throw _privateConstructorUsedError; + List? get isHeaderEnabledList => throw _privateConstructorUsedError; + List? get params => throw _privateConstructorUsedError; + List? get isParamEnabledList => throw _privateConstructorUsedError; + String? get message => throw _privateConstructorUsedError; + + /// Serializes this WebSocketRequestModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of WebSocketRequestModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $WebSocketRequestModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WebSocketRequestModelCopyWith<$Res> { + factory $WebSocketRequestModelCopyWith(WebSocketRequestModel value, + $Res Function(WebSocketRequestModel) then) = + _$WebSocketRequestModelCopyWithImpl<$Res, WebSocketRequestModel>; + @useResult + $Res call( + {String url, + ContentTypeWebSocket contentType, + bool? isConnected, + List? headers, + List? isHeaderEnabledList, + List? params, + List? isParamEnabledList, + String? message}); +} + +/// @nodoc +class _$WebSocketRequestModelCopyWithImpl<$Res, + $Val extends WebSocketRequestModel> + implements $WebSocketRequestModelCopyWith<$Res> { + _$WebSocketRequestModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of WebSocketRequestModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? contentType = null, + Object? isConnected = freezed, + Object? headers = freezed, + Object? isHeaderEnabledList = freezed, + Object? params = freezed, + Object? isParamEnabledList = freezed, + Object? message = freezed, + }) { + return _then(_value.copyWith( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + contentType: null == contentType + ? _value.contentType + : contentType // ignore: cast_nullable_to_non_nullable + as ContentTypeWebSocket, + isConnected: freezed == isConnected + ? _value.isConnected + : isConnected // ignore: cast_nullable_to_non_nullable + as bool?, + headers: freezed == headers + ? _value.headers + : headers // ignore: cast_nullable_to_non_nullable + as List?, + isHeaderEnabledList: freezed == isHeaderEnabledList + ? _value.isHeaderEnabledList + : isHeaderEnabledList // ignore: cast_nullable_to_non_nullable + as List?, + params: freezed == params + ? _value.params + : params // ignore: cast_nullable_to_non_nullable + as List?, + isParamEnabledList: freezed == isParamEnabledList + ? _value.isParamEnabledList + : isParamEnabledList // ignore: cast_nullable_to_non_nullable + as List?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WebSocketRequestModelImplCopyWith<$Res> + implements $WebSocketRequestModelCopyWith<$Res> { + factory _$$WebSocketRequestModelImplCopyWith( + _$WebSocketRequestModelImpl value, + $Res Function(_$WebSocketRequestModelImpl) then) = + __$$WebSocketRequestModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String url, + ContentTypeWebSocket contentType, + bool? isConnected, + List? headers, + List? isHeaderEnabledList, + List? params, + List? isParamEnabledList, + String? message}); +} + +/// @nodoc +class __$$WebSocketRequestModelImplCopyWithImpl<$Res> + extends _$WebSocketRequestModelCopyWithImpl<$Res, + _$WebSocketRequestModelImpl> + implements _$$WebSocketRequestModelImplCopyWith<$Res> { + __$$WebSocketRequestModelImplCopyWithImpl(_$WebSocketRequestModelImpl _value, + $Res Function(_$WebSocketRequestModelImpl) _then) + : super(_value, _then); + + /// Create a copy of WebSocketRequestModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? contentType = null, + Object? isConnected = freezed, + Object? headers = freezed, + Object? isHeaderEnabledList = freezed, + Object? params = freezed, + Object? isParamEnabledList = freezed, + Object? message = freezed, + }) { + return _then(_$WebSocketRequestModelImpl( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + contentType: null == contentType + ? _value.contentType + : contentType // ignore: cast_nullable_to_non_nullable + as ContentTypeWebSocket, + isConnected: freezed == isConnected + ? _value.isConnected + : isConnected // ignore: cast_nullable_to_non_nullable + as bool?, + headers: freezed == headers + ? _value._headers + : headers // ignore: cast_nullable_to_non_nullable + as List?, + isHeaderEnabledList: freezed == isHeaderEnabledList + ? _value._isHeaderEnabledList + : isHeaderEnabledList // ignore: cast_nullable_to_non_nullable + as List?, + params: freezed == params + ? _value._params + : params // ignore: cast_nullable_to_non_nullable + as List?, + isParamEnabledList: freezed == isParamEnabledList + ? _value._isParamEnabledList + : isParamEnabledList // ignore: cast_nullable_to_non_nullable + as List?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true) +class _$WebSocketRequestModelImpl extends _WebSocketRequestModel { + const _$WebSocketRequestModelImpl( + {this.url = "", + this.contentType = ContentTypeWebSocket.text, + this.isConnected, + final List? headers, + final List? isHeaderEnabledList, + final List? params, + final List? isParamEnabledList, + this.message}) + : _headers = headers, + _isHeaderEnabledList = isHeaderEnabledList, + _params = params, + _isParamEnabledList = isParamEnabledList, + super._(); + + factory _$WebSocketRequestModelImpl.fromJson(Map json) => + _$$WebSocketRequestModelImplFromJson(json); + + @override + @JsonKey() + final String url; + @override + @JsonKey() + final ContentTypeWebSocket contentType; + @override + final bool? isConnected; + final List? _headers; + @override + List? get headers { + final value = _headers; + if (value == null) return null; + if (_headers is EqualUnmodifiableListView) return _headers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List? _isHeaderEnabledList; + @override + List? get isHeaderEnabledList { + final value = _isHeaderEnabledList; + if (value == null) return null; + if (_isHeaderEnabledList is EqualUnmodifiableListView) + return _isHeaderEnabledList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List? _params; + @override + List? get params { + final value = _params; + if (value == null) return null; + if (_params is EqualUnmodifiableListView) return _params; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List? _isParamEnabledList; + @override + List? get isParamEnabledList { + final value = _isParamEnabledList; + if (value == null) return null; + if (_isParamEnabledList is EqualUnmodifiableListView) + return _isParamEnabledList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + final String? message; + + @override + String toString() { + return 'WebSocketRequestModel(url: $url, contentType: $contentType, isConnected: $isConnected, headers: $headers, isHeaderEnabledList: $isHeaderEnabledList, params: $params, isParamEnabledList: $isParamEnabledList, message: $message)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WebSocketRequestModelImpl && + (identical(other.url, url) || other.url == url) && + (identical(other.contentType, contentType) || + other.contentType == contentType) && + (identical(other.isConnected, isConnected) || + other.isConnected == isConnected) && + const DeepCollectionEquality().equals(other._headers, _headers) && + const DeepCollectionEquality() + .equals(other._isHeaderEnabledList, _isHeaderEnabledList) && + const DeepCollectionEquality().equals(other._params, _params) && + const DeepCollectionEquality() + .equals(other._isParamEnabledList, _isParamEnabledList) && + (identical(other.message, message) || other.message == message)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + url, + contentType, + isConnected, + const DeepCollectionEquality().hash(_headers), + const DeepCollectionEquality().hash(_isHeaderEnabledList), + const DeepCollectionEquality().hash(_params), + const DeepCollectionEquality().hash(_isParamEnabledList), + message); + + /// Create a copy of WebSocketRequestModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WebSocketRequestModelImplCopyWith<_$WebSocketRequestModelImpl> + get copyWith => __$$WebSocketRequestModelImplCopyWithImpl< + _$WebSocketRequestModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WebSocketRequestModelImplToJson( + this, + ); + } +} + +abstract class _WebSocketRequestModel extends WebSocketRequestModel { + const factory _WebSocketRequestModel( + {final String url, + final ContentTypeWebSocket contentType, + final bool? isConnected, + final List? headers, + final List? isHeaderEnabledList, + final List? params, + final List? isParamEnabledList, + final String? message}) = _$WebSocketRequestModelImpl; + const _WebSocketRequestModel._() : super._(); + + factory _WebSocketRequestModel.fromJson(Map json) = + _$WebSocketRequestModelImpl.fromJson; + + @override + String get url; + @override + ContentTypeWebSocket get contentType; + @override + bool? get isConnected; + @override + List? get headers; + @override + List? get isHeaderEnabledList; + @override + List? get params; + @override + List? get isParamEnabledList; + @override + String? get message; + + /// Create a copy of WebSocketRequestModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WebSocketRequestModelImplCopyWith<_$WebSocketRequestModelImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/apidash_core/lib/models/websocket_request_model.g.dart b/packages/apidash_core/lib/models/websocket_request_model.g.dart new file mode 100644 index 000000000..758e00992 --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_request_model.g.dart @@ -0,0 +1,49 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'websocket_request_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$WebSocketRequestModelImpl _$$WebSocketRequestModelImplFromJson(Map json) => + _$WebSocketRequestModelImpl( + url: json['url'] as String? ?? "", + contentType: $enumDecodeNullable( + _$ContentTypeWebSocketEnumMap, json['contentType']) ?? + ContentTypeWebSocket.text, + isConnected: json['isConnected'] as bool?, + headers: (json['headers'] as List?) + ?.map((e) => + NameValueModel.fromJson(Map.from(e as Map))) + .toList(), + isHeaderEnabledList: (json['isHeaderEnabledList'] as List?) + ?.map((e) => e as bool) + .toList(), + params: (json['params'] as List?) + ?.map((e) => + NameValueModel.fromJson(Map.from(e as Map))) + .toList(), + isParamEnabledList: (json['isParamEnabledList'] as List?) + ?.map((e) => e as bool) + .toList(), + message: json['message'] as String?, + ); + +Map _$$WebSocketRequestModelImplToJson( + _$WebSocketRequestModelImpl instance) => + { + 'url': instance.url, + 'contentType': _$ContentTypeWebSocketEnumMap[instance.contentType]!, + 'isConnected': instance.isConnected, + 'headers': instance.headers?.map((e) => e.toJson()).toList(), + 'isHeaderEnabledList': instance.isHeaderEnabledList, + 'params': instance.params?.map((e) => e.toJson()).toList(), + 'isParamEnabledList': instance.isParamEnabledList, + 'message': instance.message, + }; + +const _$ContentTypeWebSocketEnumMap = { + ContentTypeWebSocket.text: 'text', + ContentTypeWebSocket.binary: 'binary', +}; diff --git a/packages/apidash_core/lib/models/websocket_response_model.dart b/packages/apidash_core/lib/models/websocket_response_model.dart new file mode 100644 index 000000000..e6e8a0438 --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_response_model.dart @@ -0,0 +1,28 @@ +import 'package:apidash_core/models/websocket_frame_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + + +part 'websocket_response_model.freezed.dart'; +part 'websocket_response_model.g.dart'; + + +@freezed +class WebSocketResponseModel with _$WebSocketResponseModel { + const WebSocketResponseModel._(); + + @JsonSerializable( + explicitToJson: true, + anyMap: true, + ) + const factory WebSocketResponseModel({ + int? statusCode, + @Default([]) List frames, + Map? headers, + Map? requestHeaders, + + }) = _WebSocketResponseModel; + + factory WebSocketResponseModel.fromJson(Map json) => + _$WebSocketResponseModelFromJson(json); + +} diff --git a/packages/apidash_core/lib/models/websocket_response_model.freezed.dart b/packages/apidash_core/lib/models/websocket_response_model.freezed.dart new file mode 100644 index 000000000..3478b7aa2 --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_response_model.freezed.dart @@ -0,0 +1,270 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'websocket_response_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +WebSocketResponseModel _$WebSocketResponseModelFromJson( + Map json) { + return _WebSocketResponseModel.fromJson(json); +} + +/// @nodoc +mixin _$WebSocketResponseModel { + int? get statusCode => throw _privateConstructorUsedError; + List get frames => throw _privateConstructorUsedError; + Map? get headers => throw _privateConstructorUsedError; + Map? get requestHeaders => throw _privateConstructorUsedError; + + /// Serializes this WebSocketResponseModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of WebSocketResponseModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $WebSocketResponseModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WebSocketResponseModelCopyWith<$Res> { + factory $WebSocketResponseModelCopyWith(WebSocketResponseModel value, + $Res Function(WebSocketResponseModel) then) = + _$WebSocketResponseModelCopyWithImpl<$Res, WebSocketResponseModel>; + @useResult + $Res call( + {int? statusCode, + List frames, + Map? headers, + Map? requestHeaders}); +} + +/// @nodoc +class _$WebSocketResponseModelCopyWithImpl<$Res, + $Val extends WebSocketResponseModel> + implements $WebSocketResponseModelCopyWith<$Res> { + _$WebSocketResponseModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of WebSocketResponseModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? statusCode = freezed, + Object? frames = null, + Object? headers = freezed, + Object? requestHeaders = freezed, + }) { + return _then(_value.copyWith( + statusCode: freezed == statusCode + ? _value.statusCode + : statusCode // ignore: cast_nullable_to_non_nullable + as int?, + frames: null == frames + ? _value.frames + : frames // ignore: cast_nullable_to_non_nullable + as List, + headers: freezed == headers + ? _value.headers + : headers // ignore: cast_nullable_to_non_nullable + as Map?, + requestHeaders: freezed == requestHeaders + ? _value.requestHeaders + : requestHeaders // ignore: cast_nullable_to_non_nullable + as Map?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WebSocketResponseModelImplCopyWith<$Res> + implements $WebSocketResponseModelCopyWith<$Res> { + factory _$$WebSocketResponseModelImplCopyWith( + _$WebSocketResponseModelImpl value, + $Res Function(_$WebSocketResponseModelImpl) then) = + __$$WebSocketResponseModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int? statusCode, + List frames, + Map? headers, + Map? requestHeaders}); +} + +/// @nodoc +class __$$WebSocketResponseModelImplCopyWithImpl<$Res> + extends _$WebSocketResponseModelCopyWithImpl<$Res, + _$WebSocketResponseModelImpl> + implements _$$WebSocketResponseModelImplCopyWith<$Res> { + __$$WebSocketResponseModelImplCopyWithImpl( + _$WebSocketResponseModelImpl _value, + $Res Function(_$WebSocketResponseModelImpl) _then) + : super(_value, _then); + + /// Create a copy of WebSocketResponseModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? statusCode = freezed, + Object? frames = null, + Object? headers = freezed, + Object? requestHeaders = freezed, + }) { + return _then(_$WebSocketResponseModelImpl( + statusCode: freezed == statusCode + ? _value.statusCode + : statusCode // ignore: cast_nullable_to_non_nullable + as int?, + frames: null == frames + ? _value._frames + : frames // ignore: cast_nullable_to_non_nullable + as List, + headers: freezed == headers + ? _value._headers + : headers // ignore: cast_nullable_to_non_nullable + as Map?, + requestHeaders: freezed == requestHeaders + ? _value._requestHeaders + : requestHeaders // ignore: cast_nullable_to_non_nullable + as Map?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true, anyMap: true) +class _$WebSocketResponseModelImpl extends _WebSocketResponseModel { + const _$WebSocketResponseModelImpl( + {this.statusCode, + final List frames = const [], + final Map? headers, + final Map? requestHeaders}) + : _frames = frames, + _headers = headers, + _requestHeaders = requestHeaders, + super._(); + + factory _$WebSocketResponseModelImpl.fromJson(Map json) => + _$$WebSocketResponseModelImplFromJson(json); + + @override + final int? statusCode; + final List _frames; + @override + @JsonKey() + List get frames { + if (_frames is EqualUnmodifiableListView) return _frames; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_frames); + } + + final Map? _headers; + @override + Map? get headers { + final value = _headers; + if (value == null) return null; + if (_headers is EqualUnmodifiableMapView) return _headers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final Map? _requestHeaders; + @override + Map? get requestHeaders { + final value = _requestHeaders; + if (value == null) return null; + if (_requestHeaders is EqualUnmodifiableMapView) return _requestHeaders; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + String toString() { + return 'WebSocketResponseModel(statusCode: $statusCode, frames: $frames, headers: $headers, requestHeaders: $requestHeaders)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WebSocketResponseModelImpl && + (identical(other.statusCode, statusCode) || + other.statusCode == statusCode) && + const DeepCollectionEquality().equals(other._frames, _frames) && + const DeepCollectionEquality().equals(other._headers, _headers) && + const DeepCollectionEquality() + .equals(other._requestHeaders, _requestHeaders)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + statusCode, + const DeepCollectionEquality().hash(_frames), + const DeepCollectionEquality().hash(_headers), + const DeepCollectionEquality().hash(_requestHeaders)); + + /// Create a copy of WebSocketResponseModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$WebSocketResponseModelImplCopyWith<_$WebSocketResponseModelImpl> + get copyWith => __$$WebSocketResponseModelImplCopyWithImpl< + _$WebSocketResponseModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WebSocketResponseModelImplToJson( + this, + ); + } +} + +abstract class _WebSocketResponseModel extends WebSocketResponseModel { + const factory _WebSocketResponseModel( + {final int? statusCode, + final List frames, + final Map? headers, + final Map? requestHeaders}) = + _$WebSocketResponseModelImpl; + const _WebSocketResponseModel._() : super._(); + + factory _WebSocketResponseModel.fromJson(Map json) = + _$WebSocketResponseModelImpl.fromJson; + + @override + int? get statusCode; + @override + List get frames; + @override + Map? get headers; + @override + Map? get requestHeaders; + + /// Create a copy of WebSocketResponseModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$WebSocketResponseModelImplCopyWith<_$WebSocketResponseModelImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/apidash_core/lib/models/websocket_response_model.g.dart b/packages/apidash_core/lib/models/websocket_response_model.g.dart new file mode 100644 index 000000000..7dccd6070 --- /dev/null +++ b/packages/apidash_core/lib/models/websocket_response_model.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'websocket_response_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$WebSocketResponseModelImpl _$$WebSocketResponseModelImplFromJson(Map json) => + _$WebSocketResponseModelImpl( + statusCode: (json['statusCode'] as num?)?.toInt(), + frames: (json['frames'] as List?) + ?.map((e) => WebSocketFrameModel.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + headers: (json['headers'] as Map?)?.map( + (k, e) => MapEntry(k as String, e as String), + ), + requestHeaders: (json['requestHeaders'] as Map?)?.map( + (k, e) => MapEntry(k as String, e as String), + ), + ); + +Map _$$WebSocketResponseModelImplToJson( + _$WebSocketResponseModelImpl instance) => + { + 'statusCode': instance.statusCode, + 'frames': instance.frames.map((e) => e.toJson()).toList(), + 'headers': instance.headers, + 'requestHeaders': instance.requestHeaders, + }; diff --git a/packages/apidash_core/lib/services/http_client_manager.dart b/packages/apidash_core/lib/services/http_client_manager.dart index bec23214f..94d87cf14 100644 --- a/packages/apidash_core/lib/services/http_client_manager.dart +++ b/packages/apidash_core/lib/services/http_client_manager.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; + http.Client createHttpClientWithNoSSL() { var ioClient = HttpClient() ..badCertificateCallback = @@ -22,20 +23,23 @@ class HttpClientManager { } HttpClientManager._internal(); - + http.Client createClient( String requestId, { bool noSSL = false, }) { + final client = (noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client(); _clients[requestId] = client; return client; } + void cancelRequest(String? requestId) { if (requestId != null && _clients.containsKey(requestId)) { - _clients[requestId]?.close(); + + _clients[requestId]?.close(); _clients.remove(requestId); _cancelledRequests.addLast(requestId); diff --git a/packages/apidash_core/lib/services/http_service.dart b/packages/apidash_core/lib/services/http_service.dart index ad06a21d4..1a70b25e5 100644 --- a/packages/apidash_core/lib/services/http_service.dart +++ b/packages/apidash_core/lib/services/http_service.dart @@ -122,6 +122,7 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest( ); } stopwatch.stop(); + return (response, stopwatch.elapsed, null); } catch (e) { if (httpClientManager.wasRequestCancelled(requestId)) { diff --git a/packages/apidash_core/lib/services/services.dart b/packages/apidash_core/lib/services/services.dart index d155e9c70..8849874f4 100644 --- a/packages/apidash_core/lib/services/services.dart +++ b/packages/apidash_core/lib/services/services.dart @@ -1,2 +1,4 @@ export 'http_client_manager.dart'; export 'http_service.dart'; +export 'websocket_service.dart'; +export 'web_socket_manager.dart'; diff --git a/packages/apidash_core/lib/services/web_socket_manager.dart b/packages/apidash_core/lib/services/web_socket_manager.dart new file mode 100644 index 000000000..aff737b40 --- /dev/null +++ b/packages/apidash_core/lib/services/web_socket_manager.dart @@ -0,0 +1,68 @@ +import 'package:seed/seed.dart'; +import './websocket_service.dart'; +class WebSocketManager { + static final WebSocketManager _instance = WebSocketManager._internal(); + final Map _clients = {}; + + factory WebSocketManager() { + return _instance; + } + + WebSocketManager._internal(); + + WebSocketClient createWebSocketClient( + String requestId, { + bool noSSL = false, + }) { + final client = WebSocketClient(); + _clients[requestId] = client; + return client; + } + + WebSocketClient? getClient(String requestId) { + return _clients[requestId]; + } + + Future disconnect(String requestId) async{ + await _clients[requestId]?.disconnect(); + } + +Future<(String?,DateTime?)> connect(String requestId,String url,List? headers,List? params) async { + if (_clients.containsKey(requestId)) { + return _clients[requestId]!.connect(url,headers,params); + } + return (null,null); + + } + + Future<(String?,DateTime?,String?)> sendText(String requestId,String message) async { + if (_clients.containsKey(requestId)) { + return _clients[requestId]!.sendText(message); + } + return (null,null,null); + } + + Future<(String?,DateTime?,String?)> sendBinary(String requestId,String message) async { + if (_clients.containsKey(requestId)) { + return _clients[requestId]!.sendBinary(message); + } + return (null,null,null); + } + + + Future listen(String requestId,Future Function(dynamic message) onMessage,{Future Function(dynamic error)? onError, Future Function()? onDone,bool? cancelOnError}) async { + if (_clients.containsKey(requestId)) { + return _clients[requestId]!.listen( + onMessage, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); + } + } + Future setPingInterval(String requestId,Duration? interval) async { + if (_clients.containsKey(requestId)) { + _clients[requestId]!.pingInterval = interval; + } + } +} diff --git a/packages/apidash_core/lib/services/websocket_service.dart b/packages/apidash_core/lib/services/websocket_service.dart new file mode 100644 index 000000000..46945857c --- /dev/null +++ b/packages/apidash_core/lib/services/websocket_service.dart @@ -0,0 +1,90 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:flutter/foundation.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; +import 'package:web_socket_channel/status.dart' as status; +import 'package:web_socket_channel/io.dart'; + +class WebSocketClient { + late WebSocketChannel _channel; + StreamSubscription? _subscription; + Duration? pingInterval; + + + WebSocketClient(); + + + Future<(String?,DateTime?)> connect(String url,List? headers,List? params) async { + try { + + String urlWithParams = getValidRequestUri(url,params,defaultUriScheme:SupportedUriSchemes.wss).$1.toString(); + if(!kIsWeb){ + final WebSocket ioWebSocket = await WebSocket.connect( + urlWithParams, + headers: headers != null ? rowsToMap(headers)! : null); + _channel = IOWebSocketChannel(ioWebSocket); + + ioWebSocket.pingInterval = pingInterval; + + }else{ + _channel = WebSocketChannel.connect(Uri.parse(urlWithParams)); + } + await _channel.ready; + return (kMsgConnected,DateTime.now()); + } catch (e) { + return (e.toString(),DateTime.now()); + } + } + + + Future<(String?,DateTime?,String?)> sendText(String message)async { + try{ + _channel.sink.add(message); + return (message,DateTime.now(),null); + + }catch(e){ + return (null,DateTime.now(),e.toString()); + + } + + } + + Future<(String?,DateTime?,String?)> sendBinary(String message)async { + try{ + Uint8List binary = Uint8List.fromList(utf8.encode(message)); + + _channel.sink.add(binary); + return (message,DateTime.now(),null); + + }catch(e){ + return (null,DateTime.now(),e.toString()); + + } + + } + + Future listen(Future Function(dynamic message) onMessage, + {Future Function(dynamic error)? onError, Future Function()? onDone,bool? cancelOnError}) async{ + _subscription = _channel.stream.listen( + (message) { + onMessage(message); + }, + onError: (error) { + if (onError != null) onError(error); + }, + onDone: () { + if (onDone != null) onDone(); + }, + cancelOnError: cancelOnError ?? true, + ); + } + + + Future disconnect({int closeCode = status.normalClosure, String? reason})async { + _subscription?.cancel(); + _channel.sink.close(closeCode, reason); + } +} + diff --git a/packages/apidash_core/lib/utils/http_request_utils.dart b/packages/apidash_core/lib/utils/http_request_utils.dart index b5eab42cc..f3e95c82f 100644 --- a/packages/apidash_core/lib/utils/http_request_utils.dart +++ b/packages/apidash_core/lib/utils/http_request_utils.dart @@ -98,5 +98,6 @@ String? getRequestBody(APIType type, HttpRequestModel httpRequestModel) { ? httpRequestModel.body : null, APIType.graphql => getGraphQLBody(httpRequestModel), + APIType.webSocket => null, }; } diff --git a/packages/apidash_core/lib/utils/uri_utils.dart b/packages/apidash_core/lib/utils/uri_utils.dart index b111a2e08..400707f7e 100644 --- a/packages/apidash_core/lib/utils/uri_utils.dart +++ b/packages/apidash_core/lib/utils/uri_utils.dart @@ -31,7 +31,12 @@ String stripUrlParams(String url) { } if (kLocalhostRegex.hasMatch(url)) { - url = '${SupportedUriSchemes.http.name}://$url'; + if(defaultUriScheme == SupportedUriSchemes.https){ + url = '${SupportedUriSchemes.http.name}://$url'; + }else if(defaultUriScheme == SupportedUriSchemes.wss){ + url = '${SupportedUriSchemes.ws.name}://$url'; + } + } Uri? uri = Uri.tryParse(url); if (uri == null) { @@ -58,7 +63,10 @@ String stripUrlParams(String url) { Map urlQueryParams = uri.queryParameters; queryParams = mergeMaps(urlQueryParams, queryParams); } + uri = uri.replace(queryParameters: queryParams); } return (uri, null); } + + diff --git a/packages/apidash_core/pubspec.yaml b/packages/apidash_core/pubspec.yaml index 13aaea079..860a579a3 100644 --- a/packages/apidash_core/pubspec.yaml +++ b/packages/apidash_core/pubspec.yaml @@ -23,6 +23,9 @@ dependencies: path: ../postman seed: ^0.0.3 xml: ^6.3.0 + intl: ^0.19.0 + json_serializable: ^6.9.0 + web_socket_channel: ^3.0.1 dev_dependencies: flutter_test: @@ -30,5 +33,4 @@ dev_dependencies: build_runner: ^2.4.12 flutter_lints: ^4.0.0 freezed: ^2.5.7 - json_serializable: ^6.7.1 test: ^1.25.2 diff --git a/packages/apidash_design_system/lib/tokens/colors.dart b/packages/apidash_design_system/lib/tokens/colors.dart index 11943bd32..d0bd38033 100644 --- a/packages/apidash_design_system/lib/tokens/colors.dart +++ b/packages/apidash_design_system/lib/tokens/colors.dart @@ -26,6 +26,8 @@ final kColorHttpMethodDelete = Colors.red.shade800; final kColorGQL = Colors.pink.shade600; +final kColorWS = Colors.deepOrange.shade800; + const kHintOpacity = 0.6; const kForegroundOpacity = 0.05; const kOverlayBackgroundOpacity = 0.5; diff --git a/packages/insomnia_collection/pubspec.lock b/packages/insomnia_collection/pubspec.lock new file mode 100644 index 000000000..e2a8a7007 --- /dev/null +++ b/packages/insomnia_collection/pubspec.lock @@ -0,0 +1,573 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + url: "https://pub.dev" + source: hosted + version: "80.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + args: + dependency: transitive + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + url: "https://pub.dev" + source: hosted + version: "2.4.15" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + url: "https://pub.dev" + source: hosted + version: "8.0.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" + url: "https://pub.dev" + source: hosted + version: "8.9.4" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + url: "https://pub.dev" + source: hosted + version: "1.11.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + url: "https://pub.dev" + source: hosted + version: "2.5.8" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + http: + dependency: transitive + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" + url: "https://pub.dev" + source: hosted + version: "6.9.4" + lints: + dependency: "direct dev" + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + url: "https://pub.dev" + source: hosted + version: "1.3.5" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + url: "https://pub.dev" + source: hosted + version: "1.25.15" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + test_core: + dependency: transitive + description: + name: test_core + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + url: "https://pub.dev" + source: hosted + version: "0.6.8" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.7.0-0 <4.0.0" diff --git a/test/models/history_models.dart b/test/models/history_models.dart index e33c2f99d..c96797260 100644 --- a/test/models/history_models.dart +++ b/test/models/history_models.dart @@ -17,11 +17,13 @@ final historyMetaModel1 = HistoryMetaModel( ); /// Basic History Request model 1 -final historyRequestModel1 = HistoryRequestModel( +final historyRequestModel1 = HistoryRequestModel( historyId: 'historyId1', metaData: historyMetaModel1, httpRequestModel: httpRequestModelGet4, + webSocketRequestModel: null, httpResponseModel: responseModel, + ); final historyMetaModel2 = HistoryMetaModel( diff --git a/test/models/websocket_response_models.dart b/test/models/websocket_response_models.dart new file mode 100644 index 000000000..5b04ce3bd --- /dev/null +++ b/test/models/websocket_response_models.dart @@ -0,0 +1,44 @@ +import 'dart:typed_data'; +import 'package:apidash_core/apidash_core.dart'; + +const int statusCode = 200; +const Map headers = { + "content-length": "16", + "x-cloud-trace-context": "dad62aaf7f640300bbf629f4ae2f2f63", + "content-type": "application/json", + "date": "Sun, 23 Apr 2023 23:46:31 GMT", + "server": "Google Frontend" +}; + +const Map requestHeaders = { + "content-length": "18", + "content-type": "application/json; charset=utf-8" +}; + +const String body = '{"data":"world"}'; +Uint8List bodyBytes = Uint8List.fromList( + [123, 34, 100, 97, 116, 97, 34, 58, 34, 119, 111, 114, 108, 100, 34, 125]); +const String formattedBody = '''{ + "data": "world" +}'''; +const Duration time = Duration(milliseconds: 516); + +HttpResponseModel responseModel = HttpResponseModel( + statusCode: statusCode, + headers: headers, + requestHeaders: requestHeaders, + body: body, + formattedBody: formattedBody, + bodyBytes: bodyBytes, + time: time, +); + +Map responseModelJson = { + "statusCode": statusCode, + "headers": headers, + "requestHeaders": requestHeaders, + "body": body, + "formattedBody": formattedBody, + "bodyBytes": bodyBytes, + "time": 516000, +};