new change to use intaleq_map sdk 04-16-4

This commit is contained in:
Hamza-Ayed
2026-04-16 19:45:03 +03:00
parent 0aa1f15f25
commit a54a7a4189
850 changed files with 83282 additions and 3075 deletions

View File

@@ -0,0 +1,396 @@
import '../get_instance/src/lifecycle.dart';
import 'http/src/certificates/certificates.dart';
import 'http/src/exceptions/exceptions.dart';
import 'http/src/http.dart';
import 'http/src/response/response.dart';
import 'sockets/sockets.dart';
export 'http/src/certificates/certificates.dart';
export 'http/src/http.dart';
export 'http/src/multipart/form_data.dart';
export 'http/src/multipart/multipart_file.dart';
export 'http/src/response/response.dart';
export 'sockets/sockets.dart';
abstract class GetConnectInterface with GetLifeCycleBase {
List<GetSocket>? sockets;
GetHttpClient get httpClient;
Future<Response<T>> get<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> request<T>(
String url,
String method, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> post<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> put<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> delete<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> patch<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
});
Future<GraphQLResponse<T>> query<T>(
String query, {
String? url,
Map<String, dynamic>? variables,
Map<String, String>? headers,
});
Future<GraphQLResponse<T>> mutation<T>(
String mutation, {
String? url,
Map<String, dynamic>? variables,
Map<String, String>? headers,
});
GetSocket socket(
String url, {
Duration ping = const Duration(seconds: 5),
});
}
class GetConnect extends GetConnectInterface {
GetConnect({
this.userAgent = 'getx-client',
this.timeout = const Duration(seconds: 5),
this.followRedirects = true,
this.maxRedirects = 5,
this.sendUserAgent = false,
this.maxAuthRetries = 1,
this.allowAutoSignedCert = false,
this.withCredentials = false,
}) {
$configureLifeCycle();
}
bool allowAutoSignedCert;
String userAgent;
bool sendUserAgent;
String? baseUrl;
String defaultContentType = 'application/json; charset=utf-8';
bool followRedirects;
int maxRedirects;
int maxAuthRetries;
Decoder? defaultDecoder;
Duration timeout;
List<TrustedCertificate>? trustedCertificates;
String Function(Uri url)? findProxy;
GetHttpClient? _httpClient;
List<GetSocket>? _sockets;
bool withCredentials;
@override
List<GetSocket> get sockets => _sockets ??= <GetSocket>[];
@override
GetHttpClient get httpClient => _httpClient ??= GetHttpClient(
userAgent: userAgent,
sendUserAgent: sendUserAgent,
timeout: timeout,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
maxAuthRetries: maxAuthRetries,
allowAutoSignedCert: allowAutoSignedCert,
baseUrl: baseUrl,
trustedCertificates: trustedCertificates,
withCredentials: withCredentials,
findProxy: findProxy);
@override
Future<Response<T>> get<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
}) {
_checkIfDisposed();
return httpClient.get<T>(
url,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
Future<Response<T>> post<T>(
String? url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) {
_checkIfDisposed();
return httpClient.post<T>(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
@override
Future<Response<T>> put<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) {
_checkIfDisposed();
return httpClient.put<T>(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
@override
Future<Response<T>> patch<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) {
_checkIfDisposed();
return httpClient.patch<T>(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
@override
Future<Response<T>> request<T>(
String url,
String method, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) {
_checkIfDisposed();
return httpClient.request<T>(
url,
method,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
@override
Future<Response<T>> delete<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
}) {
_checkIfDisposed();
return httpClient.delete(
url,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
GetSocket socket(
String url, {
Duration ping = const Duration(seconds: 5),
}) {
_checkIfDisposed(isHttp: false);
final newSocket = GetSocket(_concatUrl(url)!, ping: ping);
sockets.add(newSocket);
return newSocket;
}
String? _concatUrl(String? url) {
if (url == null) return baseUrl;
return baseUrl == null ? url : baseUrl! + url;
}
/// query allow made GraphQL raw querys
/// final connect = GetConnect();
/// connect.baseUrl = 'https://countries.trevorblades.com/';
/// final response = await connect.query(
/// r"""
/// {
/// country(code: "BR") {
/// name
/// native
/// currency
/// languages {
/// code
/// name
/// }
/// }
///}
///""",
///);
///print(response.body);
@override
Future<GraphQLResponse<T>> query<T>(
String query, {
String? url,
Map<String, dynamic>? variables,
Map<String, String>? headers,
}) async {
try {
final res = await post(
url,
{'query': query, 'variables': variables},
headers: headers,
);
final listError = res.body['errors'];
if ((listError is List) && listError.isNotEmpty) {
return GraphQLResponse<T>(
graphQLErrors: listError
.map((e) => GraphQLError(
code: e['extensions']['code']?.toString(),
message: e['message']?.toString(),
))
.toList());
}
return GraphQLResponse<T>.fromResponse(res);
} on Exception catch (_) {
return GraphQLResponse<T>(graphQLErrors: [
GraphQLError(
code: null,
message: _.toString(),
)
]);
}
}
@override
Future<GraphQLResponse<T>> mutation<T>(
String mutation, {
String? url,
Map<String, dynamic>? variables,
Map<String, String>? headers,
}) async {
try {
final res = await post(
url,
{'query': mutation, 'variables': variables},
headers: headers,
);
final listError = res.body['errors'];
if ((listError is List) && listError.isNotEmpty) {
return GraphQLResponse<T>(
graphQLErrors: listError
.map((e) => GraphQLError(
code: e['extensions']['code']?.toString(),
message: e['message']?.toString(),
))
.toList());
}
return GraphQLResponse<T>.fromResponse(res);
} on Exception catch (_) {
return GraphQLResponse<T>(graphQLErrors: [
GraphQLError(
code: null,
message: _.toString(),
)
]);
}
}
bool _isDisposed = false;
bool get isDisposed => _isDisposed;
void _checkIfDisposed({bool isHttp = true}) {
if (_isDisposed) {
throw 'Can not emit events to disposed clients';
}
}
void dispose() {
if (_sockets != null) {
for (var socket in sockets) {
socket.close();
}
_sockets?.clear();
sockets = null;
}
if (_httpClient != null) {
httpClient.close();
_httpClient = null;
}
_isDisposed = true;
}
}

View File

@@ -0,0 +1,17 @@
List<int> fileToBytes(dynamic data) {
if (data is List<int>) {
return data;
} else {
throw const FormatException(
'File is not "File" or "String" or "List<int>"');
}
}
// void writeOnFile(List<int> bytes) {
// var blob = html.Blob(["data"], 'text/plain', 'native');
// var anchorElement = html.AnchorElement(
// href: html.Url.createObjectUrlFromBlob(blob).toString(),
// )
// ..setAttribute("download", "data.txt")
// ..click();
// }

View File

@@ -0,0 +1,117 @@
import 'dart:async';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import '../../certificates/certificates.dart';
import '../../exceptions/exceptions.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
import '../utils/body_decoder.dart';
/// A `dart:html` implementation of `HttpRequestBase`.
class HttpRequestImpl implements HttpRequestBase {
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
this.withCredentials = false,
String Function(Uri url)? findProxy,
});
/// The currently active XHRs.
final _xhrs = <HttpRequest>{};
///This option requires that you submit credentials for requests
///on different sites. The default is false
final bool withCredentials;
@override
Duration? timeout;
/// Sends an HTTP request and asynchronously returns the response.
@override
Future<Response<T>> send<T>(Request<T> request) async {
var bytes = await request.bodyBytes.toBytes();
HttpRequest xhr;
xhr = HttpRequest()
..timeout = timeout?.inMilliseconds
..open(request.method, '${request.url}', async: true); // check this
_xhrs.add(xhr);
xhr
..responseType = 'blob'
..withCredentials = withCredentials;
request.headers.forEach(xhr.setRequestHeader);
var completer = Completer<Response<T>>();
xhr.onLoad.first.then((_) {
var blob = xhr.response as Blob? ?? Blob([]);
var reader = FileReader();
reader.onLoad.first.then((_) async {
var bodyBytes = BodyBytesStream.fromBytes(reader.result as List<int>);
final stringBody =
await bodyBytesToString(bodyBytes, xhr.responseHeaders);
String? contentType;
if (xhr.responseHeaders.containsKey('content-type')) {
contentType = xhr.responseHeaders['content-type'];
} else {
contentType = 'application/json';
}
// xhr.responseHeaders.containsKey(key)
final body = bodyDecoded<T>(
request,
stringBody,
contentType,
);
final response = Response<T>(
bodyBytes: bodyBytes,
statusCode: xhr.status,
request: request,
headers: xhr.responseHeaders,
statusText: xhr.statusText,
body: body,
bodyString: stringBody,
);
completer.complete(response);
});
reader.onError.first.then((error) {
completer.completeError(
GetHttpException(error.toString(), request.url),
StackTrace.current,
);
});
reader.readAsArrayBuffer(blob);
});
xhr.onError.first.then((_) {
completer.completeError(
GetHttpException('XMLHttpRequest error.', request.url),
StackTrace.current);
});
xhr.send(bytes);
try {
return await completer.future;
} finally {
_xhrs.remove(xhr);
}
}
/// Closes the client and abort all active requests.
@override
void close() {
for (var xhr in _xhrs) {
xhr.abort();
}
}
}

View File

@@ -0,0 +1,20 @@
import 'dart:io';
List<int> fileToBytes(dynamic data) {
if (data is File) {
return data.readAsBytesSync();
} else if (data is String) {
if (File(data).existsSync()) {
return File(data).readAsBytesSync();
} else {
throw 'File $data not exists';
}
} else if (data is List<int>) {
return data;
} else {
throw const FormatException(
'File is not "File" or "String" or "List<int>"');
}
}
void writeOnFile(List<int> bytes) {}

View File

@@ -0,0 +1,99 @@
import 'dart:async';
import 'dart:io' as io;
import '../../certificates/certificates.dart';
import '../../exceptions/exceptions.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
import '../utils/body_decoder.dart';
/// A `dart:io` implementation of `HttpRequestBase`.
class HttpRequestImpl extends HttpRequestBase {
io.HttpClient? _httpClient;
io.SecurityContext? _securityContext;
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
}) {
_httpClient = io.HttpClient();
if (trustedCertificates != null) {
_securityContext = io.SecurityContext();
for (final trustedCertificate in trustedCertificates) {
_securityContext!
.setTrustedCertificatesBytes(List.from(trustedCertificate.bytes));
}
}
_httpClient = io.HttpClient(context: _securityContext);
_httpClient!.badCertificateCallback = (_, __, ___) => allowAutoSignedCert;
_httpClient!.findProxy = findProxy;
}
@override
Future<Response<T>> send<T>(Request<T> request) async {
var stream = request.bodyBytes.asBroadcastStream();
io.HttpClientRequest? ioRequest;
try {
_httpClient!.connectionTimeout = timeout;
ioRequest = (await _httpClient!.openUrl(request.method, request.url))
..followRedirects = request.followRedirects
..persistentConnection = request.persistentConnection
..maxRedirects = request.maxRedirects
..contentLength = request.contentLength ?? -1;
request.headers.forEach(ioRequest.headers.set);
var response = timeout == null
? await stream.pipe(ioRequest) as io.HttpClientResponse
: await stream.pipe(ioRequest).timeout(timeout!)
as io.HttpClientResponse;
var headers = <String, String>{};
response.headers.forEach((key, values) {
headers[key] = values.join(',');
});
final bodyBytes = (response);
final stringBody = await bodyBytesToString(bodyBytes, headers);
final body = bodyDecoded<T>(
request,
stringBody,
response.headers.contentType?.mimeType,
);
return Response(
headers: headers,
request: request,
statusCode: response.statusCode,
statusText: response.reasonPhrase,
bodyBytes: bodyBytes,
body: body,
bodyString: stringBody,
);
} on TimeoutException catch (_) {
ioRequest?.abort();
rethrow;
} on io.HttpException catch (error) {
throw GetHttpException(error.message, error.uri);
}
}
/// Closes the HttpClient.
@override
void close() {
if (_httpClient != null) {
_httpClient!.close(force: true);
_httpClient = null;
}
}
}
// extension FileExt on io.FileSystemEntity {
// String get fileName {
// return this?.path?.split(io.Platform.pathSeparator)?.last;
// }
// }

View File

@@ -0,0 +1,5 @@
void writeOnFile(List<int> bytes) {}
List<int> fileToBytes(dynamic data) {
throw UnimplementedError();
}

View File

@@ -0,0 +1,20 @@
import '../../certificates/certificates.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
class HttpRequestImpl extends HttpRequestBase {
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
});
@override
void close() {}
@override
Future<Response<T>> send<T>(Request<T> request) {
throw UnimplementedError();
}
}

View File

@@ -0,0 +1,19 @@
import '../../request/request.dart';
import '../../response/response.dart';
/// Abstract interface of [HttpRequestImpl].
abstract class HttpRequestBase {
/// Sends an HTTP [Request].
Future<Response<T>> send<T>(Request<T> request);
/// Closes the [Request] and cleans up any resources associated with it.
void close();
/// Gets and sets the timeout.
///
/// For mobile, this value will be applied for both connection and request
/// timeout.
///
/// For web, this value will be the request timeout.
Duration? timeout;
}

View File

@@ -0,0 +1,47 @@
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
import '../utils/body_decoder.dart';
typedef MockClientHandler = Future<Response> Function(Request request);
class MockClient extends HttpRequestBase {
/// The handler for than transforms request on response
final MockClientHandler _handler;
/// Creates a [MockClient] with a handler that receives [Request]s and sends
/// [Response]s.
MockClient(this._handler);
@override
Future<Response<T>> send<T>(Request<T> request) async {
var requestBody = await request.bodyBytes.toBytes();
var bodyBytes = BodyBytesStream.fromBytes(requestBody);
var response = await _handler(request);
final stringBody = await bodyBytesToString(bodyBytes, response.headers!);
var mimeType = response.headers!.containsKey('content-type')
? response.headers!['content-type']
: '';
final body = bodyDecoded<T>(
request,
stringBody,
mimeType,
);
return Response(
headers: response.headers,
request: request,
statusCode: response.statusCode,
statusText: response.statusText,
bodyBytes: bodyBytes,
body: body,
bodyString: stringBody,
);
}
@override
void close() {}
}

View File

@@ -0,0 +1,34 @@
import 'dart:convert';
import '../../../../../get_core/get_core.dart';
import '../../request/request.dart';
T? bodyDecoded<T>(Request<T> request, String stringBody, String? mimeType) {
T? body;
dynamic bodyToDecode;
if (mimeType != null && mimeType.contains('application/json')) {
try {
bodyToDecode = jsonDecode(stringBody);
} on FormatException catch (_) {
Get.log('Cannot decode server response to json');
bodyToDecode = stringBody;
}
} else {
bodyToDecode = stringBody;
}
try {
if (stringBody == '') {
body = null;
} else if (request.decoder == null) {
body = bodyToDecode as T?;
} else {
body = request.decoder!(bodyToDecode);
}
} on Exception catch (_) {
body = stringBody as T;
}
return body;
}

View File

@@ -0,0 +1,5 @@
class TrustedCertificate {
final List<int> bytes;
TrustedCertificate(this.bytes);
}

View File

@@ -0,0 +1,35 @@
class GetHttpException implements Exception {
final String message;
final Uri? uri;
GetHttpException(this.message, [this.uri]);
@override
String toString() => message;
}
class GraphQLError {
GraphQLError({this.code, this.message});
final String? message;
final String? code;
@override
String toString() => 'GETCONNECT ERROR:\n\tcode:$code\n\tmessage:$message';
}
class UnauthorizedException implements Exception {
@override
String toString() {
return 'Operation Unauthorized';
}
}
class UnexpectedFormat implements Exception {
final String message;
UnexpectedFormat(this.message);
@override
String toString() {
return 'Unexpected format: $message';
}
}

View File

@@ -0,0 +1,563 @@
import 'dart:async';
import 'dart:convert';
import '../src/certificates/certificates.dart';
import '../src/exceptions/exceptions.dart';
import '../src/multipart/form_data.dart';
import '../src/request/request.dart';
import '../src/response/response.dart';
import '../src/status/http_status.dart';
import '_http/_stub/_http_request_stub.dart'
if (dart.library.io) '_http/_io/_http_request_io.dart'
if (dart.library.html) '_http/_html/_http_request_html.dart' as platform;
import '_http/interface/request_base.dart';
import 'interceptors/get_modifiers.dart';
typedef Decoder<T> = T Function(dynamic data);
typedef Progress = Function(double percent);
class GetHttpClient {
String userAgent;
String? baseUrl;
String defaultContentType = 'application/json; charset=utf-8';
bool followRedirects;
int maxRedirects;
int maxAuthRetries;
bool sendUserAgent;
Decoder? defaultDecoder;
Duration timeout;
bool errorSafety = true;
final HttpRequestBase _httpClient;
final GetModifier _modifier;
String Function(Uri url)? findProxy;
GetHttpClient({
this.userAgent = 'getx-client',
this.timeout = const Duration(seconds: 8),
this.followRedirects = true,
this.maxRedirects = 5,
this.sendUserAgent = false,
this.maxAuthRetries = 1,
bool allowAutoSignedCert = false,
this.baseUrl,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
}) : _httpClient = platform.HttpRequestImpl(
allowAutoSignedCert: allowAutoSignedCert,
trustedCertificates: trustedCertificates,
withCredentials: withCredentials,
findProxy: findProxy,
),
_modifier = GetModifier();
void addAuthenticator<T>(RequestModifier<T> auth) {
_modifier.authenticator = auth as RequestModifier;
}
void addRequestModifier<T>(RequestModifier<T> interceptor) {
_modifier.addRequestModifier<T>(interceptor);
}
void removeRequestModifier<T>(RequestModifier<T> interceptor) {
_modifier.removeRequestModifier(interceptor);
}
void addResponseModifier<T>(ResponseModifier<T> interceptor) {
_modifier.addResponseModifier(interceptor);
}
void removeResponseModifier<T>(ResponseModifier<T> interceptor) {
_modifier.removeResponseModifier<T>(interceptor);
}
Uri _createUri(String? url, Map<String, dynamic>? query) {
if (baseUrl != null) {
url = baseUrl! + url!;
}
final uri = Uri.parse(url!);
if (query != null) {
return uri.replace(queryParameters: query);
}
return uri;
}
Future<Request<T>> _requestWithBody<T>(
String? url,
String? contentType,
dynamic body,
String method,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
) async {
List<int>? bodyBytes;
Stream<List<int>>? bodyStream;
final headers = <String, String>{};
if (sendUserAgent) {
headers['user-agent'] = userAgent;
}
if (body is FormData) {
bodyBytes = await body.toBytes();
headers['content-length'] = bodyBytes.length.toString();
headers['content-type'] =
'multipart/form-data; boundary=${body.boundary}';
} else if (contentType != null &&
contentType.toLowerCase() == 'application/x-www-form-urlencoded' &&
body is Map) {
var parts = [];
(body as Map<String, dynamic>).forEach((key, value) {
parts.add('${Uri.encodeQueryComponent(key)}='
'${Uri.encodeQueryComponent(value.toString())}');
});
var formData = parts.join('&');
bodyBytes = utf8.encode(formData);
headers['content-length'] = bodyBytes.length.toString();
headers['content-type'] = contentType;
} else if (body is Map || body is List) {
var jsonString = json.encode(body);
bodyBytes = utf8.encode(jsonString);
headers['content-length'] = bodyBytes.length.toString();
headers['content-type'] = contentType ?? defaultContentType;
} else if (body is String) {
bodyBytes = utf8.encode(body);
headers['content-length'] = bodyBytes.length.toString();
headers['content-type'] = contentType ?? defaultContentType;
} else if (body == null) {
headers['content-type'] = contentType ?? defaultContentType;
headers['content-length'] = '0';
} else {
if (!errorSafety) {
throw UnexpectedFormat('body cannot be ${body.runtimeType}');
}
}
if (bodyBytes != null) {
bodyStream = _trackProgress(bodyBytes, uploadProgress);
}
final uri = _createUri(url, query);
return Request<T>(
method: method,
url: uri,
headers: headers,
bodyBytes: bodyStream,
contentLength: bodyBytes?.length ?? 0,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
decoder: decoder,
);
}
Stream<List<int>> _trackProgress(
List<int> bodyBytes,
Progress? uploadProgress,
) {
var total = 0;
var length = bodyBytes.length;
var byteStream =
Stream.fromIterable(bodyBytes.map((i) => [i])).transform<List<int>>(
StreamTransformer.fromHandlers(handleData: (data, sink) {
total += data.length;
if (uploadProgress != null) {
var percent = total / length * 100;
uploadProgress(percent);
}
sink.add(data);
}),
);
return byteStream;
}
void _setSimpleHeaders(
Map<String, String> headers,
String? contentType,
) {
headers['content-type'] = contentType ?? defaultContentType;
if (sendUserAgent) {
headers['user-agent'] = userAgent;
}
}
Future<Response<T>> _performRequest<T>(
HandlerExecute<T> handler, {
bool authenticate = false,
int requestNumber = 1,
Map<String, String>? headers,
}) async {
var request = await handler();
headers?.forEach((key, value) {
request.headers[key] = value;
});
if (authenticate) await _modifier.authenticator!(request);
final newRequest = await _modifier.modifyRequest<T>(request);
_httpClient.timeout = timeout;
try {
var response = await _httpClient.send<T>(newRequest);
final newResponse =
await _modifier.modifyResponse<T>(newRequest, response);
if (HttpStatus.unauthorized == newResponse.statusCode &&
_modifier.authenticator != null &&
requestNumber <= maxAuthRetries) {
return _performRequest<T>(
handler,
authenticate: true,
requestNumber: requestNumber + 1,
headers: newRequest.headers,
);
} else if (HttpStatus.unauthorized == newResponse.statusCode) {
if (!errorSafety) {
throw UnauthorizedException();
} else {
return Response<T>(
request: newRequest,
headers: newResponse.headers,
statusCode: newResponse.statusCode,
body: newResponse.body,
bodyBytes: newResponse.bodyBytes,
bodyString: newResponse.bodyString,
statusText: newResponse.statusText,
);
}
}
return newResponse;
} on Exception catch (err) {
if (!errorSafety) {
throw GetHttpException(err.toString());
} else {
return Response<T>(
request: newRequest,
headers: null,
statusCode: null,
body: null,
statusText: "$err",
);
}
}
}
Future<Request<T>> _get<T>(
String url,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
) {
final headers = <String, String>{};
_setSimpleHeaders(headers, contentType);
final uri = _createUri(url, query);
return Future.value(Request<T>(
method: 'get',
url: uri,
headers: headers,
decoder: decoder ?? (defaultDecoder as Decoder<T>?),
contentLength: 0,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
));
}
Future<Request<T>> _request<T>(
String? url,
String method, {
String? contentType,
required dynamic body,
required Map<String, dynamic>? query,
Decoder<T>? decoder,
required Progress? uploadProgress,
}) {
return _requestWithBody<T>(
url,
contentType,
body,
method,
query,
decoder ?? (defaultDecoder as Decoder<T>?),
uploadProgress,
);
}
Request<T> _delete<T>(
String url,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
) {
final headers = <String, String>{};
_setSimpleHeaders(headers, contentType);
final uri = _createUri(url, query);
return Request<T>(
method: 'delete',
url: uri,
headers: headers,
decoder: decoder ?? (defaultDecoder as Decoder<T>?),
);
}
Future<Response<T>> patch<T>(
String url, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
// List<MultipartFile> files,
}) async {
try {
var response = await _performRequest<T>(
() => _request<T>(
url,
'patch',
contentType: contentType,
body: body,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
Future<Response<T>> post<T>(
String? url, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
// List<MultipartFile> files,
}) async {
try {
var response = await _performRequest<T>(
() => _request<T>(
url,
'post',
contentType: contentType,
body: body,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
Future<Response<T>> request<T>(
String url,
String method, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) async {
try {
var response = await _performRequest<T>(
() => _request<T>(
url,
method,
contentType: contentType,
query: query,
body: body,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
Future<Response<T>> put<T>(
String url, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) async {
try {
var response = await _performRequest<T>(
() => _request<T>(
url,
'put',
contentType: contentType,
query: query,
body: body,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
Future<Response<T>> get<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
}) async {
try {
var response = await _performRequest<T>(
() => _get<T>(url, contentType, query, decoder),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
// Future<Response<T>> download<T>(
// String url,
// String path, {
// Map<String, String> headers,
// String contentType = 'application/octet-stream',
// Map<String, dynamic> query,
// }) async {
// try {
// var response = await _performRequest<T>(
// () => _get<T>(url, contentType, query, null),
// headers: headers,
// );
// response.bodyBytes.listen((value) {});
// return response;
// } on Exception catch (e) {
// if (!errorSafety) {
// throw GetHttpException(e.toString());
// }
// return Future.value(Response<T>(
// statusText: 'Can not connect to server. Reason: $e',
// ));
// }
// int byteCount = 0;
// int totalBytes = httpResponse.contentLength;
// Directory appDocDir = await getApplicationDocumentsDirectory();
// String appDocPath = appDocDir.path;
// File file = File(path);
// var raf = file.openSync(mode: FileMode.write);
// Completer completer = Completer<String>();
// httpResponse.listen(
// (data) {
// byteCount += data.length;
// raf.writeFromSync(data);
// if (onDownloadProgress != null) {
// onDownloadProgress(byteCount, totalBytes);
// }
// },
// onDone: () {
// raf.closeSync();
// completer.complete(file.path);
// },
// onError: (e) {
// raf.closeSync();
// file.deleteSync();
// completer.completeError(e);
// },
// cancelOnError: true,
// );
// return completer.future;
// }
Future<Response<T>> delete<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
}) async {
try {
var response = await _performRequest<T>(
() async => _delete<T>(url, contentType, query, decoder),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
void close() {
_httpClient.close();
}
}

View File

@@ -0,0 +1,56 @@
import 'dart:async';
import '../request/request.dart';
import '../response/response.dart';
typedef RequestModifier<T> = FutureOr<Request<T>> Function(Request<T?> request);
typedef ResponseModifier<T> = FutureOr Function(
Request<T?> request, Response<T?> response);
typedef HandlerExecute<T> = Future<Request<T>> Function();
class GetModifier<S> {
final _requestModifiers = <RequestModifier>[];
final _responseModifiers = <ResponseModifier>[];
RequestModifier? authenticator;
void addRequestModifier<T>(RequestModifier<T> interceptor) {
_requestModifiers.add(interceptor as RequestModifier);
}
void removeRequestModifier<T>(RequestModifier<T> interceptor) {
_requestModifiers.remove(interceptor);
}
void addResponseModifier<T>(ResponseModifier<T> interceptor) {
_responseModifiers.add(interceptor as ResponseModifier);
}
void removeResponseModifier<T>(ResponseModifier<T> interceptor) {
_requestModifiers.remove(interceptor);
}
Future<Request<T>> modifyRequest<T>(Request<T> request) async {
var newRequest = request;
if (_requestModifiers.isNotEmpty) {
for (var interceptor in _requestModifiers) {
newRequest = await interceptor(newRequest) as Request<T>;
}
}
return newRequest;
}
Future<Response<T>> modifyResponse<T>(
Request<T> request, Response<T> response) async {
var newResponse = response;
if (_responseModifiers.isNotEmpty) {
for (var interceptor in _responseModifiers) {
newResponse = await interceptor(request, response) as Response<T>;
}
}
return newResponse;
}
}

View File

@@ -0,0 +1,116 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import '../request/request.dart';
import '../utils/utils.dart';
import 'multipart_file.dart';
class FormData {
FormData(Map<String, dynamic> map) : boundary = _getBoundary() {
map.forEach((key, value) {
if (value == null) return;
if (value is MultipartFile) {
files.add(MapEntry(key, value));
} else if (value is List<MultipartFile>) {
files.addAll(value.map((e) => MapEntry(key, e)));
} else if (value is List) {
fields.addAll(value.map((e) => MapEntry(key, e.toString())));
} else {
fields.add(MapEntry(key, value.toString()));
}
});
}
static const int _maxBoundaryLength = 70;
static String _getBoundary() {
final random = Random();
var list = List<int>.generate(_maxBoundaryLength - GET_BOUNDARY.length,
(_) => boundaryCharacters[random.nextInt(boundaryCharacters.length)],
growable: false);
return '$GET_BOUNDARY${String.fromCharCodes(list)}';
}
final String boundary;
/// The form fields to send for this request.
final fields = <MapEntry<String, String>>[];
/// The files to send for this request
final files = <MapEntry<String, MultipartFile>>[];
/// Returns the header string for a field. The return value is guaranteed to
/// contain only ASCII characters.
String _fieldHeader(String name, String value) {
var header =
'content-disposition: form-data; name="${browserEncode(name)}"';
if (!isPlainAscii(value)) {
header = '$header\r\n'
'content-type: text/plain; charset=utf-8\r\n'
'content-transfer-encoding: binary';
}
return '$header\r\n\r\n';
}
/// Returns the header string for a file. The return value is guaranteed to
/// contain only ASCII characters.
String _fileHeader(MapEntry<String, MultipartFile> file) {
var header =
'content-disposition: form-data; name="${browserEncode(file.key)}"';
header = '$header; filename="${browserEncode(file.value.filename)}"';
header = '$header\r\n'
'content-type: ${file.value.contentType}';
return '$header\r\n\r\n';
}
/// The length of the request body from this [FormData]
int get length {
var length = 0;
for (final item in fields) {
length += '--'.length +
_maxBoundaryLength +
'\r\n'.length +
utf8.encode(_fieldHeader(item.key, item.value)).length +
utf8.encode(item.value).length +
'\r\n'.length;
}
for (var file in files) {
length += '--'.length +
_maxBoundaryLength +
'\r\n'.length +
utf8.encode(_fileHeader(file)).length +
file.value.length! +
'\r\n'.length;
}
return length + '--'.length + _maxBoundaryLength + '--\r\n'.length;
}
Future<List<int>> toBytes() {
return BodyBytesStream(_encode()).toBytes();
}
Stream<List<int>> _encode() async* {
const line = [13, 10];
final separator = utf8.encode('--$boundary\r\n');
final close = utf8.encode('--$boundary--\r\n');
for (var field in fields) {
yield separator;
yield utf8.encode(_fieldHeader(field.key, field.value));
yield utf8.encode(field.value);
yield line;
}
for (final file in files) {
yield separator;
yield utf8.encode(_fileHeader(file));
yield* file.value.stream!;
yield line;
}
yield close;
}
}

View File

@@ -0,0 +1,30 @@
import '../_http/_stub/_file_decoder_stub.dart'
if (dart.library.html) '../_http/_html/_file_decoder_html.dart'
if (dart.library.io) '../_http/_io/_file_decoder_io.dart' as decoder;
import '../request/request.dart';
class MultipartFile {
MultipartFile(
dynamic data, {
required this.filename,
this.contentType = 'application/octet-stream',
}) : _bytes = decoder.fileToBytes(data) {
_length = _bytes.length;
_stream = BodyBytesStream.fromBytes(_bytes);
}
final List<int> _bytes;
final String contentType;
/// This stream will emit the file content of File.
Stream<List<int>>? _stream;
int? _length;
Stream<List<int>>? get stream => _stream;
int? get length => _length;
final String filename;
}

View File

@@ -0,0 +1,131 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import '../http.dart';
import '../multipart/form_data.dart';
class Request<T> {
/// Headers attach to this [Request]
final Map<String, String> headers;
/// The [Uri] from request
final Uri url;
final Decoder<T>? decoder;
/// The Http Method from this [Request]
/// ex: `GET`,`POST`,`PUT`,`DELETE`
final String method;
final int? contentLength;
/// The BodyBytesStream of body from this [Request]
final Stream<List<int>> bodyBytes;
/// When true, the client will follow redirects to resolves this [Request]
final bool followRedirects;
/// The maximum number of redirects if [followRedirects] is true.
final int maxRedirects;
final bool persistentConnection;
final FormData? files;
const Request._({
required this.method,
required this.bodyBytes,
required this.url,
required this.headers,
required this.contentLength,
required this.followRedirects,
required this.maxRedirects,
required this.files,
required this.persistentConnection,
required this.decoder,
});
factory Request({
required Uri url,
required String method,
required Map<String, String> headers,
Stream<List<int>>? bodyBytes,
bool followRedirects = true,
int maxRedirects = 4,
int? contentLength,
FormData? files,
bool persistentConnection = true,
Decoder<T>? decoder,
}) {
if (followRedirects) {
assert(maxRedirects > 0);
}
return Request._(
url: url,
method: method,
bodyBytes: bodyBytes ??= BodyBytesStream.fromBytes(const []),
headers: Map.from(headers),
followRedirects: followRedirects,
maxRedirects: maxRedirects,
contentLength: contentLength,
files: files,
persistentConnection: persistentConnection,
decoder: decoder,
);
}
Request<T> copyWith({
Uri? url,
String? method,
Map<String, String>? headers,
Stream<List<int>>? bodyBytes,
bool? followRedirects,
int? maxRedirects,
int? contentLength,
FormData? files,
bool? persistentConnection,
Decoder<T>? decoder,
bool appendHeader = true,
}) {
// If appendHeader is set to true, we will merge origin headers with that
if (appendHeader && headers != null) {
headers.addAll(this.headers);
}
return Request<T>._(
url: url ?? this.url,
method: method ?? this.method,
bodyBytes: bodyBytes ?? this.bodyBytes,
headers: headers == null ? this.headers : Map.from(headers),
followRedirects: followRedirects ?? this.followRedirects,
maxRedirects: maxRedirects ?? this.maxRedirects,
contentLength: contentLength ?? this.contentLength,
files: files ?? this.files,
persistentConnection: persistentConnection ?? this.persistentConnection,
decoder: decoder ?? this.decoder,
);
}
}
extension BodyBytesStream on Stream<List<int>> {
static Stream<List<int>> fromBytes(List<int> bytes) =>
Stream.fromIterable([bytes]);
Future<Uint8List> toBytes() {
var completer = Completer<Uint8List>();
var sink = ByteConversionSink.withCallback(
(bytes) => completer.complete(
Uint8List.fromList(bytes),
),
);
listen((val) => sink.add(val),
onError: completer.completeError,
onDone: sink.close,
cancelOnError: true);
return completer.future;
}
Future<String> bytesToString([Encoding encoding = utf8]) =>
encoding.decodeStream(this);
}

View File

@@ -0,0 +1,270 @@
import 'dart:collection';
import 'dart:convert';
import '../exceptions/exceptions.dart';
import '../request/request.dart';
import '../status/http_status.dart';
class GraphQLResponse<T> extends Response<T> {
final List<GraphQLError>? graphQLErrors;
GraphQLResponse({T? body, this.graphQLErrors}) : super(body: body);
GraphQLResponse.fromResponse(Response res)
: graphQLErrors = null,
super(
request: res.request,
statusCode: res.statusCode,
bodyBytes: res.bodyBytes,
bodyString: res.bodyString,
statusText: res.statusText,
headers: res.headers,
body: res.body['data'] as T?);
}
class Response<T> {
const Response({
this.request,
this.statusCode,
this.bodyBytes,
this.bodyString,
this.statusText = '',
this.headers = const {},
this.body,
});
/// The Http [Request] linked with this [Response].
final Request? request;
/// The response headers.
final Map<String, String>? headers;
/// The status code returned by the server.
final int? statusCode;
/// Human-readable context for [statusCode].
final String? statusText;
/// [HttpStatus] from [Response]. `status.connectionError` is true
/// when statusCode is null. `status.isUnauthorized` is true when
/// statusCode is equal `401`. `status.isNotFound` is true when
/// statusCode is equal `404`. `status.isServerError` is true when
/// statusCode is between `500` and `599`.
HttpStatus get status => HttpStatus(statusCode);
/// `hasError` is true when statusCode is not between 200 and 299.
bool get hasError => status.hasError;
/// `isOk` is true when statusCode is between 200 and 299.
bool get isOk => !hasError;
/// `unauthorized` is true when statusCode is equal `401`.
bool get unauthorized => status.isUnauthorized;
/// The response body as a Stream of Bytes.
final Stream<List<int>>? bodyBytes;
/// The response body as a Stream of Bytes.
final String? bodyString;
/// The decoded body of this [Response]. You can access the
/// body parameters as Map
/// Ex: `body['title'];`
final T? body;
}
Future<String> bodyBytesToString(
Stream<List<int>> bodyBytes, Map<String, String> headers) {
return bodyBytes.bytesToString(_encodingForHeaders(headers));
}
/// Returns the encoding to use for a response with the given headers.
///
/// Defaults to [utf8] if the headers don't specify a charset or if that
/// charset is unknown.
Encoding _encodingForHeaders(Map<String, String> headers) =>
_encodingForCharset(_contentTypeForHeaders(headers).parameters!['charset']);
/// Returns the [Encoding] that corresponds to [charset].
///
/// Returns [fallback] if [charset] is null or if no [Encoding] was found that
/// corresponds to [charset].
Encoding _encodingForCharset(String? charset, [Encoding fallback = utf8]) {
if (charset == null) return fallback;
return Encoding.getByName(charset) ?? fallback;
}
/// Returns the MediaType object for the given headers's content-type.
///
/// Defaults to `application/octet-stream`.
HeaderValue _contentTypeForHeaders(Map<String, String> headers) {
var contentType = headers['content-type'];
if (contentType != null) return HeaderValue.parse(contentType);
return HeaderValue('application/octet-stream');
}
class HeaderValue {
String _value;
Map<String, String?>? _parameters;
Map<String, String?>? _unmodifiableParameters;
HeaderValue([this._value = '', Map<String, String>? parameters]) {
if (parameters != null) {
_parameters = HashMap<String, String>.from(parameters);
}
}
static HeaderValue parse(String value,
{String parameterSeparator = ';',
String? valueSeparator,
bool preserveBackslash = false}) {
var result = HeaderValue();
result._parse(value, parameterSeparator, valueSeparator, preserveBackslash);
return result;
}
String get value => _value;
void _ensureParameters() {
_parameters ??= HashMap<String, String>();
}
Map<String, String?>? get parameters {
_ensureParameters();
_unmodifiableParameters ??= UnmodifiableMapView(_parameters!);
return _unmodifiableParameters;
}
@override
String toString() {
var stringBuffer = StringBuffer();
stringBuffer.write(_value);
if (parameters != null && parameters!.isNotEmpty) {
_parameters!.forEach((name, value) {
stringBuffer
..write('; ')
..write(name)
..write('=')
..write(value);
});
}
return stringBuffer.toString();
}
void _parse(String value, String parameterSeparator, String? valueSeparator,
bool preserveBackslash) {
var index = 0;
bool done() => index == value.length;
void bump() {
while (!done()) {
if (value[index] != ' ' && value[index] != '\t') return;
index++;
}
}
String parseValue() {
var start = index;
while (!done()) {
if (value[index] == ' ' ||
value[index] == '\t' ||
value[index] == valueSeparator ||
value[index] == parameterSeparator) {
break;
}
index++;
}
return value.substring(start, index);
}
void expect(String expected) {
if (done() || value[index] != expected) {
throw StateError('Failed to parse header value');
}
index++;
}
void maybeExpect(String expected) {
if (value[index] == expected) index++;
}
void parseParameters() {
var parameters = HashMap<String, String?>();
_parameters = UnmodifiableMapView(parameters);
String parseParameterName() {
var start = index;
while (!done()) {
if (value[index] == ' ' ||
value[index] == '\t' ||
value[index] == '=' ||
value[index] == parameterSeparator ||
value[index] == valueSeparator) {
break;
}
index++;
}
return value.substring(start, index).toLowerCase();
}
String? parseParameterValue() {
if (!done() && value[index] == '"') {
var stringBuffer = StringBuffer();
index++;
while (!done()) {
if (value[index] == '\\') {
if (index + 1 == value.length) {
throw StateError('Failed to parse header value');
}
if (preserveBackslash && value[index + 1] != '"') {
stringBuffer.write(value[index]);
}
index++;
} else if (value[index] == '"') {
index++;
break;
}
stringBuffer.write(value[index]);
index++;
}
return stringBuffer.toString();
} else {
var val = parseValue();
return val == '' ? null : val;
}
}
while (!done()) {
bump();
if (done()) return;
var name = parseParameterName();
bump();
if (done()) {
parameters[name] = null;
return;
}
maybeExpect('=');
bump();
if (done()) {
parameters[name] = null;
return;
}
var valueParameter = parseParameterValue();
if (name == 'charset' && valueParameter != null) {
valueParameter = valueParameter.toLowerCase();
}
parameters[name] = valueParameter;
bump();
if (done()) return;
if (value[index] == valueSeparator) return;
expect(parameterSeparator);
}
}
bump();
_value = parseValue();
bump();
if (done()) return;
maybeExpect(parameterSeparator);
parseParameters();
}
}

View File

@@ -0,0 +1,92 @@
class HttpStatus {
HttpStatus(this.code);
final int? code;
static const int continue_ = 100;
static const int switchingProtocols = 101;
static const int processing = 102;
static const int earlyHints = 103;
static const int ok = 200;
static const int created = 201;
static const int accepted = 202;
static const int nonAuthoritativeInformation = 203;
static const int noContent = 204;
static const int resetContent = 205;
static const int partialContent = 206;
static const int multiStatus = 207;
static const int alreadyReported = 208;
static const int imUsed = 226;
static const int multipleChoices = 300;
static const int movedPermanently = 301;
static const int found = 302;
static const int movedTemporarily = 302; // Common alias for found.
static const int seeOther = 303;
static const int notModified = 304;
static const int useProxy = 305;
static const int switchProxy = 306;
static const int temporaryRedirect = 307;
static const int permanentRedirect = 308;
static const int badRequest = 400;
static const int unauthorized = 401;
static const int paymentRequired = 402;
static const int forbidden = 403;
static const int notFound = 404;
static const int methodNotAllowed = 405;
static const int notAcceptable = 406;
static const int proxyAuthenticationRequired = 407;
static const int requestTimeout = 408;
static const int conflict = 409;
static const int gone = 410;
static const int lengthRequired = 411;
static const int preconditionFailed = 412;
static const int requestEntityTooLarge = 413;
static const int requestUriTooLong = 414;
static const int unsupportedMediaType = 415;
static const int requestedRangeNotSatisfiable = 416;
static const int expectationFailed = 417;
static const int imATeapot = 418;
static const int misdirectedRequest = 421;
static const int unprocessableEntity = 422;
static const int locked = 423;
static const int failedDependency = 424;
static const int tooEarly = 425;
static const int upgradeRequired = 426;
static const int preconditionRequired = 428;
static const int tooManyRequests = 429;
static const int requestHeaderFieldsTooLarge = 431;
static const int connectionClosedWithoutResponse = 444;
static const int unavailableForLegalReasons = 451;
static const int clientClosedRequest = 499;
static const int internalServerError = 500;
static const int notImplemented = 501;
static const int badGateway = 502;
static const int serviceUnavailable = 503;
static const int gatewayTimeout = 504;
static const int httpVersionNotSupported = 505;
static const int variantAlsoNegotiates = 506;
static const int insufficientStorage = 507;
static const int loopDetected = 508;
static const int notExtended = 510;
static const int networkAuthenticationRequired = 511;
static const int networkConnectTimeoutError = 599;
bool get connectionError => code == null;
bool get isUnauthorized => code == unauthorized;
bool get isForbidden => code == forbidden;
bool get isNotFound => code == notFound;
bool get isServerError =>
between(internalServerError, networkConnectTimeoutError);
bool between(int begin, int end) {
return !connectionError && code! >= begin && code! <= end;
}
bool get isOk => between(200, 299);
bool get hasError => !isOk;
}

View File

@@ -0,0 +1,139 @@
import 'dart:convert';
bool isTokenChar(int byte) {
return byte > 31 && byte < 128 && !SEPARATOR_MAP[byte];
}
bool isValueChar(int byte) {
return (byte > 31 && byte < 128) ||
(byte == CharCode.SP) ||
(byte == CharCode.HT);
}
class CharCode {
static const int HT = 9;
static const int LF = 10;
static const int CR = 13;
static const int SP = 32;
static const int COMMA = 44;
static const int SLASH = 47;
static const int ZERO = 48;
static const int ONE = 49;
static const int COLON = 58;
static const int SEMI_COLON = 59;
}
const bool F = false;
const bool T = true;
const SEPARATOR_MAP = [
F, F, F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, T, F, T, F, F, F, F, F, T, T, F, F, T, F, F, T, //
F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, T, T, T, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, T, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F
];
String validateField(String field) {
for (var i = 0; i < field.length; i++) {
if (!isTokenChar(field.codeUnitAt(i))) {
throw FormatException(
'Invalid HTTP header field name: ${json.encode(field)}', field, i);
}
}
return field.toLowerCase();
}
// Stream<List<int>> toBodyBytesStream(Stream<List<int>> stream) {
// return (stream);
// }
final _asciiOnly = RegExp(r'^[\x00-\x7F]+$');
final newlineRegExp = RegExp(r'\r\n|\r|\n');
/// Returns whether [string] is composed entirely of ASCII-compatible
/// characters.
bool isPlainAscii(String string) => _asciiOnly.hasMatch(string);
const String GET_BOUNDARY = 'getx-http-boundary-';
/// Encode [value] like browsers
String browserEncode(String value) {
return value.replaceAll(newlineRegExp, '%0D%0A').replaceAll('"', '%22');
}
const List<int> boundaryCharacters = <int>[
43,
95,
45,
46,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122
];

View File

@@ -0,0 +1,9 @@
import 'src/sockets_stub.dart'
if (dart.library.html) 'src/sockets_html.dart'
if (dart.library.io) 'src/sockets_io.dart';
class GetSocket extends BaseWebSocket {
GetSocket(String url,
{Duration ping = const Duration(seconds: 5), bool allowSelfSigned = true})
: super(url, ping: ping, allowSelfSigned: allowSelfSigned);
}

View File

@@ -0,0 +1,101 @@
import 'dart:convert';
/// Signature for [SocketNotifier.addCloses].
typedef CloseSocket = void Function(Close);
/// Signature for [SocketNotifier.addMessages].
typedef MessageSocket = void Function(dynamic val);
/// Signature for [SocketNotifier.open].
typedef OpenSocket = void Function();
/// Wrapper class to message and reason from SocketNotifier
class Close {
final String? message;
final int? reason;
Close(this.message, this.reason);
@override
String toString() {
return 'Closed by server [$reason => $message]!';
}
}
/// This class manages the transmission of messages over websockets using
/// GetConnect
class SocketNotifier {
List<void Function(dynamic)>? _onMessages = <MessageSocket>[];
Map<String, void Function(dynamic)>? _onEvents = <String, MessageSocket>{};
List<void Function(Close)>? _onCloses = <CloseSocket>[];
List<void Function(Close)>? _onErrors = <CloseSocket>[];
late OpenSocket open;
/// subscribe to close events
void addCloses(CloseSocket socket) {
_onCloses!.add(socket);
}
/// subscribe to error events
void addErrors(CloseSocket socket) {
_onErrors!.add((socket));
}
/// subscribe to named events
void addEvents(String event, MessageSocket socket) {
_onEvents![event] = socket;
}
/// subscribe to message events
void addMessages(MessageSocket socket) {
_onMessages!.add((socket));
}
/// Dispose messages, events, closes and errors subscriptions
void dispose() {
_onMessages = null;
_onEvents = null;
_onCloses = null;
_onErrors = null;
}
/// Notify all subscriptions on [addCloses]
void notifyClose(Close err) {
for (var item in _onCloses!) {
item(err);
}
}
/// Notify all subscriptions on [addMessages]
void notifyData(dynamic data) {
for (var item in _onMessages!) {
item(data);
}
if (data is String) {
_tryOn(data);
}
}
/// Notify all subscriptions on [addErrors]
void notifyError(Close err) {
// rooms.removeWhere((key, value) => value.contains(_ws));
for (var item in _onErrors!) {
item(err);
}
}
void _tryOn(String message) {
try {
var msg = jsonDecode(message);
final event = msg['type'];
final data = msg['data'];
if (_onEvents!.containsKey(event)) {
_onEvents![event]!(data);
}
// ignore: avoid_catches_without_on_clauses
} catch (_) {
return;
}
}
}

View File

@@ -0,0 +1,116 @@
import 'dart:async';
import 'dart:convert';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import '../../../get_core/get_core.dart';
import 'socket_notifier.dart';
class BaseWebSocket {
String url;
WebSocket? socket;
SocketNotifier? socketNotifier = SocketNotifier();
Duration ping;
bool isDisposed = false;
bool allowSelfSigned;
ConnectionStatus? connectionStatus;
Timer? _t;
BaseWebSocket(
this.url, {
this.ping = const Duration(seconds: 5),
this.allowSelfSigned = true,
}) {
url = url.startsWith('https')
? url.replaceAll('https:', 'wss:')
: url.replaceAll('http:', 'ws:');
}
void close([int? status, String? reason]) {
socket?.close(status, reason);
}
// ignore: use_setters_to_change_properties
void connect() {
try {
connectionStatus = ConnectionStatus.connecting;
socket = WebSocket(url);
socket!.onOpen.listen((e) {
socketNotifier?.open();
_t = Timer?.periodic(ping, (t) {
socket!.send('');
});
connectionStatus = ConnectionStatus.connected;
});
socket!.onMessage.listen((event) {
socketNotifier!.notifyData(event.data);
});
socket!.onClose.listen((e) {
_t?.cancel();
connectionStatus = ConnectionStatus.closed;
socketNotifier!.notifyClose(Close(e.reason, e.code));
});
socket!.onError.listen((event) {
_t?.cancel();
socketNotifier!.notifyError(Close(event.toString(), 0));
connectionStatus = ConnectionStatus.closed;
});
} on Exception catch (e) {
_t?.cancel();
socketNotifier!.notifyError(Close(e.toString(), 500));
connectionStatus = ConnectionStatus.closed;
// close(500, e.toString());
}
}
void dispose() {
socketNotifier!.dispose();
socketNotifier = null;
isDisposed = true;
}
void emit(String event, dynamic data) {
send(jsonEncode({'type': event, 'data': data}));
}
void on(String event, MessageSocket message) {
socketNotifier!.addEvents(event, message);
}
void onClose(CloseSocket fn) {
socketNotifier!.addCloses(fn);
}
void onError(CloseSocket fn) {
socketNotifier!.addErrors(fn);
}
void onMessage(MessageSocket fn) {
socketNotifier!.addMessages(fn);
}
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier!.open = fn;
}
void send(dynamic data) {
if (connectionStatus == ConnectionStatus.closed) {
connect();
}
if (socket != null && socket!.readyState == WebSocket.OPEN) {
socket!.send(data);
} else {
Get.log('WebSocket not connected, message $data not sent');
}
}
}
enum ConnectionStatus {
connecting,
connected,
closed,
}

View File

@@ -0,0 +1,138 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import '../../../get_core/get_core.dart';
import 'socket_notifier.dart';
class BaseWebSocket {
String url;
WebSocket? socket;
SocketNotifier? socketNotifier = SocketNotifier();
bool isDisposed = false;
Duration ping;
bool allowSelfSigned;
ConnectionStatus? connectionStatus;
BaseWebSocket(
this.url, {
this.ping = const Duration(seconds: 5),
this.allowSelfSigned = true,
});
void close([int? status, String? reason]) {
socket?.close(status, reason);
}
// ignore: use_setters_to_change_properties
Future connect() async {
if (isDisposed) {
socketNotifier = SocketNotifier();
}
try {
connectionStatus = ConnectionStatus.connecting;
socket = allowSelfSigned
? await _connectForSelfSignedCert(url)
: await WebSocket.connect(url);
socket!.pingInterval = ping;
socketNotifier?.open();
connectionStatus = ConnectionStatus.connected;
socket!.listen((data) {
socketNotifier!.notifyData(data);
}, onError: (err) {
socketNotifier!.notifyError(Close(err.toString(), 1005));
}, onDone: () {
connectionStatus = ConnectionStatus.closed;
socketNotifier!
.notifyClose(Close('Connection Closed', socket!.closeCode));
}, cancelOnError: true);
return;
} on SocketException catch (e) {
connectionStatus = ConnectionStatus.closed;
socketNotifier!
.notifyError(Close(e.osError!.message, e.osError!.errorCode));
return;
}
}
void dispose() {
socketNotifier!.dispose();
socketNotifier = null;
isDisposed = true;
}
void emit(String event, dynamic data) {
send(jsonEncode({'type': event, 'data': data}));
}
void on(String event, MessageSocket message) {
socketNotifier!.addEvents(event, message);
}
void onClose(CloseSocket fn) {
socketNotifier!.addCloses(fn);
}
void onError(CloseSocket fn) {
socketNotifier!.addErrors(fn);
}
void onMessage(MessageSocket fn) {
socketNotifier!.addMessages(fn);
}
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier!.open = fn;
}
void send(dynamic data) async {
if (connectionStatus == ConnectionStatus.closed) {
await connect();
}
if (socket != null) {
socket!.add(data);
}
}
Future<WebSocket> _connectForSelfSignedCert(String url) async {
try {
var r = Random();
var key = base64.encode(List<int>.generate(8, (_) => r.nextInt(255)));
var client = HttpClient(context: SecurityContext());
client.badCertificateCallback = (cert, host, port) {
Get.log(
'BaseWebSocket: Allow self-signed certificate => $host:$port. ');
return true;
};
var request = await client.getUrl(Uri.parse(url));
request.headers.add('Connection', 'Upgrade');
request.headers.add('Upgrade', 'websocket');
request.headers.add('Sec-WebSocket-Version', '13');
request.headers.add('Sec-WebSocket-Key', key.toLowerCase());
var response = await request.close();
// ignore: close_sinks
var socket = await response.detachSocket();
var webSocket = WebSocket.fromUpgradedSocket(
socket,
serverSide: false,
);
return webSocket;
} on Exception catch (_) {
rethrow;
}
}
}
enum ConnectionStatus {
connecting,
connected,
closed,
}

View File

@@ -0,0 +1,54 @@
import './socket_notifier.dart';
class BaseWebSocket {
String url;
Duration ping;
bool allowSelfSigned;
BaseWebSocket(
this.url, {
this.ping = const Duration(seconds: 5),
this.allowSelfSigned = true,
}) {
throw 'To use sockets you need dart:io or dart:html';
}
Future connect() async {
throw 'To use sockets you need dart:io or dart:html';
}
void onOpen(OpenSocket fn) {
throw 'To use sockets you need dart:io or dart:html';
}
void onClose(CloseSocket fn) {
throw 'To use sockets you need dart:io or dart:html';
}
void onError(CloseSocket fn) {
throw 'To use sockets you need dart:io or dart:html';
}
void onMessage(MessageSocket fn) {
throw 'To use sockets you need dart:io or dart:html';
}
void on(String event, MessageSocket message) {
throw 'To use sockets you need dart:io or dart:html';
}
void close([int? status, String? reason]) {
throw 'To use sockets you need dart:io or dart:html';
}
void send(dynamic data) async {
throw 'To use sockets you need dart:io or dart:html';
}
void dispose() {
throw 'To use sockets you need dart:io or dart:html';
}
void emit(String event, dynamic data) {
throw 'To use sockets you need dart:io or dart:html';
}
}