platform/lib/base_angel_client.dart

439 lines
12 KiB
Dart
Raw Normal View History

2016-12-09 00:24:07 +00:00
import 'dart:async';
2018-06-23 00:18:38 +00:00
import 'dart:convert' show Encoding;
2017-09-24 04:12:53 +00:00
import 'package:angel_http_exception/angel_http_exception.dart';
2018-08-26 22:41:01 +00:00
import 'dart:convert';
2016-12-09 00:24:07 +00:00
import 'package:http/src/base_client.dart' as http;
import 'package:http/src/base_request.dart' as http;
import 'package:http/src/request.dart' as http;
import 'package:http/src/response.dart' as http;
import 'package:http/src/streamed_response.dart' as http;
2019-01-06 02:08:31 +00:00
import 'package:path/path.dart' as p;
2016-12-09 00:24:07 +00:00
import 'angel_client.dart';
const Map<String, String> _readHeaders = const {'Accept': 'application/json'};
2016-12-13 16:34:22 +00:00
const Map<String, String> _writeHeaders = const {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
2016-12-09 00:24:07 +00:00
2019-01-06 02:08:31 +00:00
Map<String, String> _buildQuery(Map<String, dynamic> params) {
return params?.map((k, v) => new MapEntry(k, v.toString()));
2016-12-09 00:24:07 +00:00
}
2017-03-29 01:52:19 +00:00
bool _invalid(http.Response response) =>
response.statusCode == null ||
response.statusCode < 200 ||
response.statusCode >= 300;
2018-11-04 01:34:21 +00:00
AngelHttpException failure(http.Response response,
{error, String message, StackTrace stack}) {
2016-12-09 00:24:07 +00:00
try {
2019-01-06 02:08:31 +00:00
var v = json.decode(response.body);
2016-12-09 00:24:07 +00:00
2018-11-04 01:34:21 +00:00
if (v is Map && (v['is_error'] == true) || v['isError'] == true) {
return new AngelHttpException.fromMap(v as Map);
2016-12-09 00:24:07 +00:00
} else {
return new AngelHttpException(error,
2018-11-04 01:34:21 +00:00
message: message ??
'Unhandled exception while connecting to Angel backend.',
2016-12-09 00:24:07 +00:00
statusCode: response.statusCode,
stackTrace: stack);
}
} catch (e, st) {
return new AngelHttpException(error ?? e,
2018-11-04 01:34:21 +00:00
message: message ??
'Angel backend did not return JSON - an error likely occurred.',
2016-12-09 00:24:07 +00:00
statusCode: response.statusCode,
stackTrace: stack ?? st);
}
}
abstract class BaseAngelClient extends Angel {
2017-06-03 17:43:01 +00:00
final StreamController<AngelAuthResult> _onAuthenticated =
new StreamController<AngelAuthResult>();
final List<Service> _services = [];
2016-12-09 00:24:07 +00:00
final http.BaseClient client;
2017-06-03 17:43:01 +00:00
@override
Stream<AngelAuthResult> get onAuthenticated => _onAuthenticated.stream;
2016-12-09 00:24:07 +00:00
BaseAngelClient(this.client, String basePath) : super(basePath);
@override
Future<AngelAuthResult> authenticate(
2017-03-07 21:54:13 +00:00
{String type,
2016-12-09 00:24:07 +00:00
credentials,
2019-01-06 02:08:31 +00:00
String authEndpoint = '/auth',
@deprecated String reviveEndpoint = '/auth/token'}) async {
type ??= 'token';
var segments = baseUrl.pathSegments
.followedBy(p.split(authEndpoint))
.followedBy([type]);
var url = baseUrl.replace(path: p.joinAll(segments));
http.Response response;
if (credentials != null) {
response = await post(url,
body: json.encode(credentials), headers: _writeHeaders);
2016-12-09 00:24:07 +00:00
} else {
2019-01-06 02:08:31 +00:00
response = await post(url, headers: _writeHeaders);
}
2016-12-09 00:24:07 +00:00
2019-01-06 02:08:31 +00:00
if (_invalid(response)) {
throw failure(response);
}
2016-12-09 00:24:07 +00:00
2019-01-06 02:08:31 +00:00
try {
var v = json.decode(response.body);
2016-12-09 00:24:07 +00:00
2019-01-06 02:08:31 +00:00
if (v is! Map ||
!(v as Map).containsKey('data') ||
!(v as Map).containsKey('token')) {
throw new AngelHttpException.notAuthenticated(
message: "Auth endpoint '$url' did not return a proper response.");
2016-12-09 00:24:07 +00:00
}
2019-01-06 02:08:31 +00:00
var r = new AngelAuthResult.fromMap(v as Map);
_onAuthenticated.add(r);
return r;
} on AngelHttpException {
rethrow;
} catch (e, st) {
throw failure(response, error: e, stack: st);
2016-12-09 00:24:07 +00:00
}
}
2019-01-06 02:08:31 +00:00
Future<void> close() async {
2016-12-10 14:50:05 +00:00
client.close();
2017-06-03 17:43:01 +00:00
_onAuthenticated.close();
Future.wait(_services.map((s) => s.close())).then((_) {
_services.clear();
});
2016-12-10 14:50:05 +00:00
}
2019-01-06 02:08:31 +00:00
Future<void> logout() async {
2017-03-29 01:52:19 +00:00
authToken = null;
}
2019-01-06 02:08:31 +00:00
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
if (authToken?.isNotEmpty == true)
request.headers['authorization'] ??= 'Bearer $authToken';
return client.send(request);
}
2017-01-25 23:25:31 +00:00
/// Sends a non-streaming [Request] and returns a non-streaming [Response].
Future<http.Response> sendUnstreamed(
String method, url, Map<String, String> headers,
[body, Encoding encoding]) async {
2018-08-26 22:41:01 +00:00
var request =
new http.Request(method, url is Uri ? url : Uri.parse(url.toString()));
2017-01-25 23:25:31 +00:00
if (headers != null) request.headers.addAll(headers);
if (encoding != null) request.encoding = encoding;
if (body != null) {
if (body is String) {
request.body = body;
2019-01-06 02:08:31 +00:00
} else if (body is List<int>) {
request.bodyBytes = new List<int>.from(body);
} else if (body is Map<String, String>) {
request.bodyFields = new Map<String, String>.from(body);
2017-01-25 23:25:31 +00:00
} else {
2019-01-06 02:08:31 +00:00
throw new ArgumentError.value(body, 'body',
'must be a String, List<int>, or Map<String, String>.');
2017-01-25 23:25:31 +00:00
}
}
2019-01-06 02:08:31 +00:00
return http.Response.fromStream(await send(request));
2017-01-25 23:25:31 +00:00
}
2016-12-09 00:24:07 +00:00
@override
Service<Id, Data> service<Id, Data>(String path,
{Type type, AngelDeserializer<Data> deserializer}) {
2019-01-06 02:08:31 +00:00
var url = baseUrl.replace(path: p.join(baseUrl.path, path));
var s = new BaseAngelService<Id, Data>(client, this, url,
2016-12-13 16:35:35 +00:00
deserializer: deserializer);
2017-06-03 17:43:01 +00:00
_services.add(s);
return s;
2016-12-09 00:24:07 +00:00
}
2016-12-10 17:15:54 +00:00
2019-01-06 02:08:31 +00:00
Uri _join(url) {
var u = url is Uri ? url : Uri.parse(url.toString());
if (u.hasScheme || u.hasAuthority) return u;
return baseUrl.replace(path: p.join(baseUrl.path, u.path));
2016-12-10 17:15:54 +00:00
}
@override
2019-01-06 02:08:31 +00:00
Future<http.Response> delete(url, {Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('DELETE', _join(url), headers);
2016-12-10 17:15:54 +00:00
}
@override
2019-01-06 02:08:31 +00:00
Future<http.Response> get(url, {Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('GET', _join(url), headers);
2016-12-10 17:15:54 +00:00
}
@override
2019-01-06 02:08:31 +00:00
Future<http.Response> head(url, {Map<String, String> headers}) async {
2017-01-25 23:25:31 +00:00
return sendUnstreamed('HEAD', _join(url), headers);
2016-12-10 17:15:54 +00:00
}
@override
2019-01-06 02:08:31 +00:00
Future<http.Response> patch(url,
{body, Map<String, String> headers, Encoding encoding}) async {
return sendUnstreamed('PATCH', _join(url), headers, body, encoding);
2016-12-10 17:15:54 +00:00
}
@override
2019-01-06 02:08:31 +00:00
Future<http.Response> post(url,
{body, Map<String, String> headers, Encoding encoding}) async {
return sendUnstreamed('POST', _join(url), headers, body, encoding);
2016-12-10 17:15:54 +00:00
}
@override
2019-01-06 02:08:31 +00:00
Future<http.Response> put(url,
{body, Map<String, String> headers, Encoding encoding}) async {
return sendUnstreamed('PUT', _join(url), headers, body, encoding);
2016-12-10 17:15:54 +00:00
}
2016-12-09 00:24:07 +00:00
}
class BaseAngelService<Id, Data> extends Service<Id, Data> {
2016-12-09 00:24:07 +00:00
@override
2017-01-25 23:25:31 +00:00
final BaseAngelClient app;
2019-01-06 02:08:31 +00:00
final Uri baseUrl;
2016-12-09 00:24:07 +00:00
final http.BaseClient client;
final AngelDeserializer<Data> deserializer;
2016-12-09 00:24:07 +00:00
2018-11-04 01:34:21 +00:00
final StreamController<List<Data>> _onIndexed = new StreamController();
final StreamController<Data> _onRead = new StreamController(),
2017-09-24 04:15:40 +00:00
_onCreated = new StreamController(),
_onModified = new StreamController(),
_onUpdated = new StreamController(),
_onRemoved = new StreamController();
2017-06-03 17:43:01 +00:00
@override
2018-11-04 01:34:21 +00:00
Stream<List<Data>> get onIndexed => _onIndexed.stream;
2017-06-03 17:43:01 +00:00
@override
Stream<Data> get onRead => _onRead.stream;
2017-06-03 17:43:01 +00:00
@override
Stream<Data> get onCreated => _onCreated.stream;
2017-06-03 17:43:01 +00:00
@override
Stream<Data> get onModified => _onModified.stream;
2017-06-03 17:43:01 +00:00
@override
Stream<Data> get onUpdated => _onUpdated.stream;
2017-06-03 17:43:01 +00:00
@override
Stream<Data> get onRemoved => _onRemoved.stream;
2017-06-03 17:43:01 +00:00
@override
Future close() async {
_onIndexed.close();
_onRead.close();
_onCreated.close();
_onModified.close();
_onUpdated.close();
_onRemoved.close();
}
2019-01-06 02:08:31 +00:00
BaseAngelService(this.client, this.app, baseUrl, {this.deserializer})
: this.baseUrl = baseUrl is Uri ? baseUrl : Uri.parse(baseUrl.toString());
/// Use [baseUrl] instead.
@deprecated
String get basePath => baseUrl.toString();
2016-12-13 16:35:35 +00:00
Data deserialize(x) {
return deserializer != null ? deserializer(x) : x as Data;
2016-12-13 16:35:35 +00:00
}
2016-12-09 00:24:07 +00:00
makeBody(x) {
2018-06-23 00:18:38 +00:00
return json.encode(x);
2016-12-09 00:24:07 +00:00
}
Future<http.StreamedResponse> send(http.BaseRequest request) {
if (app.authToken != null && app.authToken.isNotEmpty) {
request.headers['Authorization'] = 'Bearer ${app.authToken}';
}
return client.send(request);
}
@override
2018-11-04 01:34:21 +00:00
Future<List<Data>> index([Map<String, dynamic> params]) async {
2019-01-06 02:08:31 +00:00
var url = baseUrl.replace(queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('GET', url, _readHeaders);
2016-12-09 00:24:07 +00:00
try {
2017-03-29 01:52:19 +00:00
if (_invalid(response)) {
2017-12-21 20:08:45 +00:00
if (_onIndexed.hasListener)
_onIndexed.addError(failure(response));
else
throw failure(response);
2016-12-09 00:24:07 +00:00
}
2019-01-06 02:08:31 +00:00
var v = json.decode(response.body) as List;
2018-06-23 00:18:38 +00:00
var r = v.map(deserialize).toList();
2017-06-03 17:43:01 +00:00
_onIndexed.add(r);
return r;
2016-12-09 00:24:07 +00:00
} catch (e, st) {
2017-12-21 20:08:45 +00:00
if (_onIndexed.hasListener)
_onIndexed.addError(e, st);
else
throw failure(response, error: e, stack: st);
2016-12-09 00:24:07 +00:00
}
2018-11-04 01:34:21 +00:00
return null;
2016-12-09 00:24:07 +00:00
}
@override
2018-11-04 01:34:21 +00:00
Future<Data> read(id, [Map<String, dynamic> params]) async {
2019-01-06 02:08:31 +00:00
var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('GET', url, _readHeaders);
2016-12-09 00:24:07 +00:00
try {
2017-03-29 01:52:19 +00:00
if (_invalid(response)) {
2017-12-21 20:08:45 +00:00
if (_onRead.hasListener)
_onRead.addError(failure(response));
else
throw failure(response);
2016-12-09 00:24:07 +00:00
}
2018-06-23 00:18:38 +00:00
var r = deserialize(json.decode(response.body));
2017-06-03 17:43:01 +00:00
_onRead.add(r);
return r;
2016-12-09 00:24:07 +00:00
} catch (e, st) {
2017-12-21 20:08:45 +00:00
if (_onRead.hasListener)
_onRead.addError(e, st);
else
throw failure(response, error: e, stack: st);
2016-12-09 00:24:07 +00:00
}
2018-11-04 01:34:21 +00:00
return null;
2016-12-09 00:24:07 +00:00
}
@override
2018-11-04 01:34:21 +00:00
Future<Data> create(data, [Map<String, dynamic> params]) async {
2019-01-06 02:08:31 +00:00
var url = baseUrl.replace(queryParameters: _buildQuery(params));
var response =
await app.sendUnstreamed('POST', url, _writeHeaders, makeBody(data));
2016-12-09 00:24:07 +00:00
try {
2017-03-29 01:52:19 +00:00
if (_invalid(response)) {
2017-12-21 20:08:45 +00:00
if (_onCreated.hasListener)
_onCreated.addError(failure(response));
else
throw failure(response);
2016-12-09 00:24:07 +00:00
}
2018-06-23 00:18:38 +00:00
var r = deserialize(json.decode(response.body));
2017-06-03 17:43:01 +00:00
_onCreated.add(r);
return r;
2016-12-09 00:24:07 +00:00
} catch (e, st) {
2017-12-21 20:08:45 +00:00
if (_onCreated.hasListener)
_onCreated.addError(e, st);
else
throw failure(response, error: e, stack: st);
2016-12-09 00:24:07 +00:00
}
2018-11-04 01:34:21 +00:00
return null;
2016-12-09 00:24:07 +00:00
}
@override
2018-11-04 01:34:21 +00:00
Future<Data> modify(id, data, [Map<String, dynamic> params]) async {
2019-01-06 02:08:31 +00:00
var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params));
var response =
await app.sendUnstreamed('PATCH', url, _writeHeaders, makeBody(data));
2016-12-09 00:24:07 +00:00
try {
2017-03-29 01:52:19 +00:00
if (_invalid(response)) {
2017-12-21 20:08:45 +00:00
if (_onModified.hasListener)
_onModified.addError(failure(response));
else
throw failure(response);
2016-12-09 00:24:07 +00:00
}
2018-06-23 00:18:38 +00:00
var r = deserialize(json.decode(response.body));
2017-06-03 17:43:01 +00:00
_onModified.add(r);
return r;
2016-12-09 00:24:07 +00:00
} catch (e, st) {
2017-12-21 20:08:45 +00:00
if (_onModified.hasListener)
_onModified.addError(e, st);
else
throw failure(response, error: e, stack: st);
2016-12-09 00:24:07 +00:00
}
2018-11-04 01:34:21 +00:00
return null;
2016-12-09 00:24:07 +00:00
}
@override
2018-11-04 01:34:21 +00:00
Future<Data> update(id, data, [Map<String, dynamic> params]) async {
2019-01-06 02:08:31 +00:00
var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params));
var response =
await app.sendUnstreamed('POST', url, _writeHeaders, makeBody(data));
2016-12-09 00:24:07 +00:00
try {
2017-03-29 01:52:19 +00:00
if (_invalid(response)) {
2017-12-21 20:08:45 +00:00
if (_onUpdated.hasListener)
_onUpdated.addError(failure(response));
else
throw failure(response);
2016-12-09 00:24:07 +00:00
}
2018-06-23 00:18:38 +00:00
var r = deserialize(json.decode(response.body));
2017-06-03 17:43:01 +00:00
_onUpdated.add(r);
return r;
2016-12-09 00:24:07 +00:00
} catch (e, st) {
2017-12-21 20:08:45 +00:00
if (_onUpdated.hasListener)
_onUpdated.addError(e, st);
else
throw failure(response, error: e, stack: st);
2016-12-09 00:24:07 +00:00
}
2018-11-04 01:34:21 +00:00
return null;
2016-12-09 00:24:07 +00:00
}
@override
2018-11-04 01:34:21 +00:00
Future<Data> remove(id, [Map<String, dynamic> params]) async {
2019-01-06 02:08:31 +00:00
var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('DELETE', url, _readHeaders);
2016-12-09 00:24:07 +00:00
try {
2017-03-29 01:52:19 +00:00
if (_invalid(response)) {
2017-12-21 20:08:45 +00:00
if (_onRemoved.hasListener)
_onRemoved.addError(failure(response));
else
throw failure(response);
2016-12-09 00:24:07 +00:00
}
2018-06-23 00:18:38 +00:00
var r = deserialize(json.decode(response.body));
2017-06-03 17:43:01 +00:00
_onRemoved.add(r);
return r;
2016-12-09 00:24:07 +00:00
} catch (e, st) {
2017-12-21 20:08:45 +00:00
if (_onRemoved.hasListener)
_onRemoved.addError(e, st);
else
throw failure(response, error: e, stack: st);
2016-12-09 00:24:07 +00:00
}
2018-11-04 01:34:21 +00:00
return null;
2016-12-09 00:24:07 +00:00
}
}