Frontend first draft
This commit is contained in:
2
frontend/pshared/lib/utils/http/client.dart
Normal file
2
frontend/pshared/lib/utils/http/client.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
export 'client/io.dart'
|
||||
if (dart.library.html) 'http_client/web.dart';
|
||||
55
frontend/pshared/lib/utils/http/client/io.dart
Normal file
55
frontend/pshared/lib/utils/http/client/io.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'dart:io' as io show HttpClient, HttpHeaders;
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart';
|
||||
|
||||
|
||||
const _sessionCookie = 'session_id';
|
||||
|
||||
@override
|
||||
http.Client buildHttpClient() => _SessionClient(IOClient(io.HttpClient()));
|
||||
|
||||
class _SessionClient extends http.BaseClient {
|
||||
final http.Client _inner;
|
||||
String? _sessionId;
|
||||
|
||||
_SessionClient(this._inner);
|
||||
|
||||
@override
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
||||
if (_sessionId != null) {
|
||||
request.headers[io.HttpHeaders.cookieHeader] = '$_sessionCookie=$_sessionId';
|
||||
}
|
||||
|
||||
request.followRedirects = false;
|
||||
request.maxRedirects = 0;
|
||||
|
||||
http.StreamedResponse response = await _inner.send(request);
|
||||
|
||||
_captureCookie(response.headers[io.HttpHeaders.setCookieHeader]);
|
||||
|
||||
while (response.isRedirect) {
|
||||
final location = response.headers['location'];
|
||||
if (location == null) break;
|
||||
|
||||
final redirected = http.Request(request.method, Uri.parse(location))
|
||||
..headers.addAll(request.headers)
|
||||
..bodyBytes = await response.stream.toBytes()
|
||||
..followRedirects = false
|
||||
..maxRedirects = 0;
|
||||
|
||||
response = await _inner.send(redirected);
|
||||
_captureCookie(response.headers[io.HttpHeaders.setCookieHeader]);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
void _captureCookie(String? setCookieHeader) {
|
||||
if (setCookieHeader == null) return;
|
||||
final match = RegExp('$_sessionCookie=([^;]+)',
|
||||
caseSensitive: false)
|
||||
.firstMatch(setCookieHeader);
|
||||
if (match != null) _sessionId = match.group(1);
|
||||
}
|
||||
}
|
||||
4
frontend/pshared/lib/utils/http/client/stub.dart
Normal file
4
frontend/pshared/lib/utils/http/client/stub.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
import 'package:http/http.dart';
|
||||
|
||||
|
||||
Client buildHttpClient() => throw UnsupportedError('buildHttpClient() was called without a proper platform implementation.');
|
||||
9
frontend/pshared/lib/utils/http/client/web.dart
Normal file
9
frontend/pshared/lib/utils/http/client/web.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'package:http/browser_client.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
@override
|
||||
http.Client buildHttpClient() {
|
||||
final bc = BrowserClient();
|
||||
bc.withCredentials = true;
|
||||
return bc;
|
||||
}
|
||||
165
frontend/pshared/lib/utils/http/requests.dart
Normal file
165
frontend/pshared/lib/utils/http/requests.dart
Normal file
@@ -0,0 +1,165 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
import 'package:pshared/api/responses/file_uploaded.dart';
|
||||
import 'package:pshared/api/responses/message.dart';
|
||||
import 'package:pshared/api/responses/error/connectivity.dart';
|
||||
import 'package:pshared/api/responses/error/server.dart';
|
||||
import 'package:pshared/config/constants.dart';
|
||||
|
||||
|
||||
Uri _uri(String service, String url) {
|
||||
// Ensure the base URL ends with a slash
|
||||
final normalizedBaseUrl = Constants.apiUrl.endsWith('/')
|
||||
? Constants.apiUrl
|
||||
: '${Constants.apiUrl}/';
|
||||
|
||||
// Remove leading slash from service, if any
|
||||
final normalizedService = service.startsWith('/') ? service.substring(1) : service;
|
||||
|
||||
// Remove leading slash from url, if any
|
||||
final normalizedUrl = url.startsWith('/') ? url.substring(1) : url;
|
||||
|
||||
// Only append a trailing slash to the service
|
||||
// if the URL is non-empty
|
||||
final serviceWithOptionalSlash = normalizedUrl.isNotEmpty ? '$normalizedService/' : normalizedService;
|
||||
|
||||
return Uri.parse(normalizedBaseUrl).resolve(serviceWithOptionalSlash).resolve(normalizedUrl);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Map<String, String> _authHeader(Map<String, String> headers, String? authToken) {
|
||||
if (authToken != null && authToken.isNotEmpty) {
|
||||
headers['Authorization'] = 'Bearer $authToken';
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
Map<String, String> _headers({String? authToken}) {
|
||||
final headers = {'Content-Type': 'application/json'};
|
||||
return _authHeader(headers, authToken);
|
||||
}
|
||||
|
||||
Future<http.Response> postRequest(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
|
||||
final response = await http.post(_uri(service, url),
|
||||
headers: _headers(authToken: authToken),
|
||||
body: json.encode(body),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<http.Response> getRequest(String service, String url, {String? authToken}) async {
|
||||
final response = await http.get(_uri(service, url),
|
||||
headers: _headers(authToken: authToken),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<http.Response> putRequest(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
|
||||
final response = await http.put(_uri(service, url),
|
||||
headers: _headers(authToken: authToken),
|
||||
body: json.encode(body),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<http.Response> patchRequest(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
|
||||
final response = await http.patch(_uri(service, url),
|
||||
headers: _headers(authToken: authToken),
|
||||
body: json.encode(body),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<http.Response> deleteRequest(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
|
||||
final response = await http.delete(_uri(service, url),
|
||||
headers: _headers(authToken: authToken),
|
||||
body: json.encode(body),
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<http.StreamedResponse> _fileUploadRequest(String service, String url, String fileName, String fileType, String mediaType, List<int> bytes, {String? authToken}) async {
|
||||
var request = http.MultipartRequest('POST', _uri(service, url));
|
||||
|
||||
var multipartFile = http.MultipartFile.fromBytes(
|
||||
fileType,
|
||||
bytes,
|
||||
contentType: MediaType.parse(mediaType),
|
||||
filename: fileName,
|
||||
);
|
||||
|
||||
request.files.add(multipartFile);
|
||||
|
||||
if (authToken != null && authToken.isNotEmpty) {
|
||||
request.headers['Authorization'] = 'Bearer $authToken';
|
||||
}
|
||||
|
||||
return request.send();
|
||||
}
|
||||
|
||||
void _throwConnectivityError(http.Response response, Object e) {
|
||||
throw ConnectivityError(
|
||||
code: response.statusCode,
|
||||
message: e is FormatException
|
||||
? 'Invalid response format. error: ${e.toString()}'
|
||||
: 'Unknown error occurred, error: ${e.toString()}',
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _handleResponse(Future<http.Response> r) async {
|
||||
late http.Response response;
|
||||
try {
|
||||
response = await r;
|
||||
} catch(e) {
|
||||
throw ConnectivityError(message: e.toString());
|
||||
}
|
||||
|
||||
late HTTPMessage message;
|
||||
try {
|
||||
message = HTTPMessage.fromJson(json.decode(response.body));
|
||||
} catch(e) {
|
||||
_throwConnectivityError(response, e);
|
||||
}
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
late ErrorResponse error;
|
||||
try {
|
||||
error = ErrorResponse.fromJson(message.data);
|
||||
} catch(e) {
|
||||
_throwConnectivityError(response, e);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return message.data;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getPOSTResponse(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
|
||||
return _handleResponse(postRequest(service, url, body, authToken: authToken));
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getGETResponse(String service, String url, {String? authToken}) async {
|
||||
return _handleResponse(getRequest(service, url, authToken: authToken));
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getPUTResponse(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
|
||||
return _handleResponse(putRequest(service, url, body, authToken: authToken));
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getPATCHResponse(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
|
||||
return _handleResponse(patchRequest(service, url, body, authToken: authToken));
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getDELETEResponse(String service, String url, Map<String, dynamic> body, {String? authToken}) async {
|
||||
return _handleResponse(deleteRequest(service, url, body, authToken: authToken));
|
||||
}
|
||||
|
||||
Future<FileUploaded?> getFileUploadResponse(String service, String url, String fileName, String fileType, String mediaType, List<int> bytes, {String? authToken}) async {
|
||||
final streamedResponse = await _fileUploadRequest(service, url, fileName, fileType, mediaType, bytes, authToken: authToken);
|
||||
return FileUploaded.fromJson(await _handleResponse(http.Response.fromStream(streamedResponse)));
|
||||
}
|
||||
Reference in New Issue
Block a user