OpenContacts/lib/api_client.dart

175 lines
6.1 KiB
Dart
Raw Normal View History

2023-04-29 13:18:46 -04:00
import 'dart:convert';
2023-04-30 03:01:59 -04:00
import 'dart:developer';
2023-04-30 07:39:09 -04:00
import 'package:contacts_plus/models/message.dart';
2023-04-29 13:18:46 -04:00
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'package:contacts_plus/models/authentication_data.dart';
2023-04-30 03:01:59 -04:00
import 'package:signalr_netcore/http_connection_options.dart';
import 'package:signalr_netcore/hub_connection.dart';
import 'package:signalr_netcore/hub_connection_builder.dart';
2023-04-30 07:39:09 -04:00
import 'package:signalr_netcore/ihub_protocol.dart';
import 'package:signalr_netcore/msgpack_hub_protocol.dart';
import 'package:signalr_netcore/web_supporting_http_client.dart';
2023-04-29 13:18:46 -04:00
import 'package:uuid/uuid.dart';
2023-04-30 07:39:09 -04:00
import 'package:logging/logging.dart';
2023-04-29 13:18:46 -04:00
import 'config.dart';
class ApiClient {
static const String userIdKey = "userId";
static const String machineIdKey = "machineId";
static const String tokenKey = "token";
2023-04-29 15:26:12 -04:00
static const String passwordKey = "password";
2023-04-29 13:18:46 -04:00
2023-04-30 07:39:09 -04:00
ApiClient({required AuthenticationData authenticationData}) : _authenticationData = authenticationData {
if (_authenticationData.isAuthenticated) {
hub.start();
}
2023-04-29 16:21:00 -04:00
}
2023-04-30 07:39:09 -04:00
late final NeosHub hub = NeosHub(token: authorizationHeader.values.first);
final AuthenticationData _authenticationData;
2023-04-29 15:26:12 -04:00
2023-04-30 07:39:09 -04:00
String get userId => _authenticationData.userId;
bool get isAuthenticated => _authenticationData.isAuthenticated;
2023-04-29 13:18:46 -04:00
2023-04-29 15:26:12 -04:00
static Future<AuthenticationData> tryLogin({
required String username,
required String password,
bool rememberMe=true,
bool rememberPass=false
}) async {
2023-04-29 13:18:46 -04:00
final body = {
"username": username,
"password": password,
"rememberMe": rememberMe,
"secretMachineId": const Uuid().v4(),
};
final response = await http.post(
2023-04-29 15:26:12 -04:00
buildFullUri("/UserSessions"),
2023-04-29 13:18:46 -04:00
headers: {"Content-Type": "application/json"},
body: jsonEncode(body));
if (response.statusCode == 400) {
throw "Invalid Credentials";
2023-04-29 15:26:12 -04:00
}
checkResponse(response);
2023-04-29 13:18:46 -04:00
2023-04-29 15:26:12 -04:00
final authData = AuthenticationData.fromMap(jsonDecode(response.body));
2023-04-29 13:18:46 -04:00
if (authData.isAuthenticated) {
const FlutterSecureStorage storage = FlutterSecureStorage();
await storage.write(key: userIdKey, value: authData.userId);
await storage.write(key: machineIdKey, value: authData.secretMachineId);
await storage.write(key: tokenKey, value: authData.token);
2023-04-29 15:26:12 -04:00
if (rememberPass) await storage.write(key: passwordKey, value: password);
2023-04-29 13:18:46 -04:00
}
return authData;
}
static Future<AuthenticationData> tryCachedLogin() async {
const FlutterSecureStorage storage = FlutterSecureStorage();
String? userId = await storage.read(key: userIdKey);
String? machineId = await storage.read(key: machineIdKey);
String? token = await storage.read(key: tokenKey);
2023-04-29 15:26:12 -04:00
String? password = await storage.read(key: passwordKey);
2023-04-29 13:18:46 -04:00
2023-04-29 15:26:12 -04:00
if (userId == null || machineId == null) {
2023-04-29 13:18:46 -04:00
return AuthenticationData.unauthenticated();
}
2023-04-29 15:26:12 -04:00
if (token != null) {
final response = await http.get(buildFullUri("/users/$userId"), headers: {
"Authorization": "neos $userId:$token"
});
if (response.statusCode == 200) {
return AuthenticationData(userId: userId, token: token, secretMachineId: machineId, isAuthenticated: true);
}
}
if (password != null) {
try {
userId = userId.startsWith("U-") ? userId.replaceRange(0, 2, "") : userId;
final loginResult = await tryLogin(username: userId, password: password, rememberPass: true);
if (loginResult.isAuthenticated) return loginResult;
} catch (_) {
// We don't need to notify the user if the cached login fails behind the scenes, so just ignore any exceptions.
}
2023-04-29 13:18:46 -04:00
}
return AuthenticationData.unauthenticated();
}
2023-04-29 15:26:12 -04:00
static void checkResponse(http.Response response) {
if (response.statusCode != 200) {
throw "Unknown Error${kDebugMode ? ": ${response.statusCode}|${response.body}" : ""}";
}
}
2023-04-29 13:18:46 -04:00
Map<String, String> get authorizationHeader => {
2023-04-30 07:39:09 -04:00
"Authorization": "neos ${_authenticationData.userId}:${_authenticationData.token}"
2023-04-29 13:18:46 -04:00
};
2023-04-29 15:26:12 -04:00
static Uri buildFullUri(String path) => Uri.parse("${Config.apiBaseUrl}/api$path");
Future<http.Response> get(String path, {Map<String, String>? headers}) {
2023-04-29 13:18:46 -04:00
headers ??= {};
headers.addAll(authorizationHeader);
2023-04-29 15:26:12 -04:00
return http.get(buildFullUri(path), headers: headers);
2023-04-29 13:18:46 -04:00
}
2023-04-29 15:26:12 -04:00
Future<http.Response> post(String path, {Object? body, Map<String, String>? headers}) {
2023-04-29 13:18:46 -04:00
headers ??= {};
headers["Content-Type"] = "application/json";
headers.addAll(authorizationHeader);
2023-04-29 15:26:12 -04:00
return http.post(buildFullUri(path), headers: headers, body: body);
2023-04-29 13:18:46 -04:00
}
2023-04-29 15:26:12 -04:00
Future<http.Response> put(String path, {Object? body, Map<String, String>? headers}) {
2023-04-29 13:18:46 -04:00
headers ??= {};
headers.addAll(authorizationHeader);
2023-04-29 15:26:12 -04:00
return http.put(buildFullUri(path), headers: headers, body: body);
2023-04-29 13:18:46 -04:00
}
2023-04-29 15:26:12 -04:00
Future<http.Response> delete(String path, {Map<String, String>? headers}) {
2023-04-29 13:18:46 -04:00
headers ??= {};
headers.addAll(authorizationHeader);
2023-04-29 15:26:12 -04:00
return http.delete(buildFullUri(path), headers: headers);
2023-04-29 13:18:46 -04:00
}
2023-04-29 16:21:00 -04:00
}
2023-04-30 03:01:59 -04:00
class NeosHub {
2023-04-30 07:39:09 -04:00
late final HubConnection hubConnection;
final Logger _logger = Logger("NeosHub");
NeosHub({required String token}) {
hubConnection = HubConnectionBuilder()
.withUrl(
Config.neosHubUrl,
options: HttpConnectionOptions(
headers: MessageHeaders()
..setHeaderValue("Authorization", token),
httpClient: WebSupportingHttpClient(
_logger,
),
logger: _logger,
logMessageContent: true
),
).withAutomaticReconnect().build();
hubConnection.onreconnecting(({error}) {
log("onreconnecting called with error $error");
});
hubConnection.onreconnected(({connectionId}) {
log("onreconnected called");
});
}
void start() {
hubConnection.start()?.onError((error, stackTrace) => log(error.toString())).whenComplete(() {
2023-04-30 03:01:59 -04:00
log("Hub connection established");
});
}
2023-04-30 07:39:09 -04:00
Future<void> sendMessage(Message message) async {
await hubConnection.send("SendMessage", args: [message.toMap()]);
}
}