2016-06-25 18:37:49 +00:00
|
|
|
/// Browser library for the Angel framework.
|
|
|
|
library angel_client.browser;
|
|
|
|
|
2016-11-28 03:28:41 +00:00
|
|
|
import 'dart:async' show Completer, Future;
|
2016-06-25 18:37:49 +00:00
|
|
|
import 'dart:convert' show JSON;
|
2016-11-28 03:34:04 +00:00
|
|
|
import 'dart:html' show HttpRequest, window;
|
2016-09-03 12:02:32 +00:00
|
|
|
import 'angel_client.dart';
|
2016-11-28 03:28:41 +00:00
|
|
|
import 'auth_types.dart' as auth_types;
|
2016-09-03 12:02:32 +00:00
|
|
|
export 'angel_client.dart';
|
2016-06-25 18:37:49 +00:00
|
|
|
|
|
|
|
_buildQuery(Map params) {
|
2016-11-28 03:28:41 +00:00
|
|
|
if (params == null || params == {}) return "";
|
2016-06-25 18:37:49 +00:00
|
|
|
|
|
|
|
String result = "";
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-28 03:28:41 +00:00
|
|
|
_send(HttpRequest request, [data]) {
|
|
|
|
final completer = new Completer<HttpRequest>();
|
|
|
|
|
|
|
|
request
|
|
|
|
..onLoadEnd.listen((_) {
|
|
|
|
completer.complete(request.response);
|
|
|
|
})
|
|
|
|
..onError.listen((_) {
|
|
|
|
try {
|
|
|
|
throw new Exception(
|
|
|
|
'Request failed with status code ${request.status}.');
|
|
|
|
} catch (e, st) {
|
|
|
|
completer.completeError(e, st);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-11-28 05:02:12 +00:00
|
|
|
if (data == null)
|
|
|
|
request.send();
|
2016-12-03 18:21:44 +00:00
|
|
|
else if (data is String)
|
|
|
|
request.send(data);
|
2016-11-28 05:02:12 +00:00
|
|
|
else
|
|
|
|
request.send(JSON.encode(data));
|
2016-11-28 03:28:41 +00:00
|
|
|
return completer.future;
|
|
|
|
}
|
|
|
|
|
2016-06-25 18:37:49 +00:00
|
|
|
/// Queries an Angel server via REST.
|
|
|
|
class Rest extends Angel {
|
2016-11-28 03:28:41 +00:00
|
|
|
String _authToken;
|
|
|
|
|
|
|
|
Rest(String basePath) : super(basePath);
|
|
|
|
|
|
|
|
@override
|
2016-12-03 18:21:44 +00:00
|
|
|
Future authenticate(
|
|
|
|
{String type,
|
2016-11-28 03:28:41 +00:00
|
|
|
credentials,
|
2016-11-29 00:42:02 +00:00
|
|
|
String authEndpoint: '/auth',
|
|
|
|
String reviveEndpoint: '/auth/token'}) async {
|
2016-11-28 03:34:04 +00:00
|
|
|
if (type == null) {
|
2016-12-03 18:21:44 +00:00
|
|
|
if (!window.localStorage.containsKey('token')) {
|
|
|
|
throw new Exception(
|
|
|
|
'Cannot revive token from localStorage - there is none.');
|
|
|
|
}
|
|
|
|
|
2016-11-29 00:42:02 +00:00
|
|
|
final result = new _AngelAuthResultImpl(
|
|
|
|
token: JSON.decode(window.localStorage['token']),
|
|
|
|
data: JSON.decode(window.localStorage['user']));
|
|
|
|
final completer = new Completer();
|
2016-12-03 18:21:44 +00:00
|
|
|
final request = new HttpRequest()..responseType = 'json';
|
2016-11-29 00:42:02 +00:00
|
|
|
request.open('POST', '$basePath$reviveEndpoint');
|
2016-12-03 18:21:44 +00:00
|
|
|
request.setRequestHeader('Accept', 'application/json');
|
|
|
|
request.setRequestHeader('Content-Type', 'application/json');
|
2016-11-29 00:42:02 +00:00
|
|
|
request.setRequestHeader('Authorization', 'Bearer ${result.token}');
|
|
|
|
|
|
|
|
request
|
|
|
|
..onLoadEnd.listen((_) {
|
|
|
|
final result = new _AngelAuthResultImpl.fromMap(request.response);
|
|
|
|
_authToken = result.token;
|
|
|
|
window.localStorage['token'] = JSON.encode(result.token);
|
|
|
|
window.localStorage['user'] = JSON.encode(result.data);
|
|
|
|
completer.complete(result);
|
|
|
|
})
|
|
|
|
..onError.listen((_) {
|
|
|
|
try {
|
|
|
|
throw new Exception(
|
|
|
|
'Request failed with status code ${request.status}.');
|
|
|
|
} catch (e, st) {
|
|
|
|
completer.completeError(e, st);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-12-03 18:21:44 +00:00
|
|
|
request.send(JSON.encode(result));
|
2016-11-29 00:42:02 +00:00
|
|
|
return completer.future;
|
2016-11-28 03:34:04 +00:00
|
|
|
}
|
|
|
|
|
2016-11-29 00:42:02 +00:00
|
|
|
final url = '$basePath$authEndpoint/$type';
|
2016-11-28 03:28:41 +00:00
|
|
|
|
|
|
|
if (type == auth_types.LOCAL) {
|
|
|
|
final completer = new Completer();
|
|
|
|
final request = new HttpRequest();
|
|
|
|
request.open('POST', url);
|
|
|
|
request.responseType = 'json';
|
|
|
|
request.setRequestHeader("Accept", "application/json");
|
|
|
|
request.setRequestHeader("Content-Type", "application/json");
|
|
|
|
|
|
|
|
request
|
|
|
|
..onLoadEnd.listen((_) {
|
2016-11-28 03:34:04 +00:00
|
|
|
final result = new _AngelAuthResultImpl.fromMap(request.response);
|
|
|
|
_authToken = result.token;
|
|
|
|
window.localStorage['token'] = JSON.encode(result.token);
|
|
|
|
window.localStorage['user'] = JSON.encode(result.data);
|
|
|
|
completer.complete(result);
|
2016-11-28 03:28:41 +00:00
|
|
|
})
|
|
|
|
..onError.listen((_) {
|
|
|
|
try {
|
|
|
|
throw new Exception(
|
|
|
|
'Request failed with status code ${request.status}.');
|
|
|
|
} catch (e, st) {
|
|
|
|
completer.completeError(e, st);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-11-28 05:05:20 +00:00
|
|
|
if (credentials == null)
|
|
|
|
request.send();
|
|
|
|
else
|
|
|
|
request.send(JSON.encode(credentials));
|
2016-11-28 03:28:41 +00:00
|
|
|
|
|
|
|
return completer.future;
|
|
|
|
} else {
|
|
|
|
throw new Exception('angel_client cannot authenticate as "$type" yet.');
|
|
|
|
}
|
|
|
|
}
|
2016-06-25 18:37:49 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
RestService service(String path, {Type type}) {
|
|
|
|
String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), "");
|
2016-11-28 03:28:41 +00:00
|
|
|
return new _RestServiceImpl(this, "$basePath/$uri");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class RestService extends Service {
|
|
|
|
RestService._(String basePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
class _AngelAuthResultImpl implements AngelAuthResult {
|
2016-12-03 18:21:44 +00:00
|
|
|
String _token;
|
2016-11-28 03:28:41 +00:00
|
|
|
final Map<String, dynamic> data = {};
|
2016-12-03 18:21:44 +00:00
|
|
|
String get token => _token;
|
|
|
|
|
|
|
|
_AngelAuthResultImpl({token, Map<String, dynamic> data: const {}}) {
|
|
|
|
if (token is String) _token = token;
|
2016-11-28 03:28:41 +00:00
|
|
|
|
|
|
|
this.data.addAll(data ?? {});
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
2016-11-28 03:28:41 +00:00
|
|
|
|
2016-12-03 18:21:44 +00:00
|
|
|
factory _AngelAuthResultImpl.fromMap(Map data) {
|
|
|
|
final result = new _AngelAuthResultImpl();
|
|
|
|
|
|
|
|
if (data is Map && data.containsKey('token') && data['token'] is String)
|
|
|
|
result._token = data['token'];
|
|
|
|
|
|
|
|
if (data is Map) result.data.addAll(data['data'] ?? {});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
return {'token': token, 'data': data};
|
|
|
|
}
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Queries an Angel service via REST.
|
2016-11-28 03:28:41 +00:00
|
|
|
class _RestServiceImpl extends RestService {
|
|
|
|
final Rest app;
|
|
|
|
String _basePath;
|
|
|
|
String get basePath => _basePath;
|
2016-06-25 18:37:49 +00:00
|
|
|
|
2016-11-28 03:28:41 +00:00
|
|
|
_RestServiceImpl(this.app, String basePath) : super._(basePath) {
|
|
|
|
_basePath = basePath;
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_makeBody(data) {
|
|
|
|
return JSON.encode(data);
|
|
|
|
}
|
|
|
|
|
2016-11-28 03:28:41 +00:00
|
|
|
Future<HttpRequest> buildRequest(String url,
|
|
|
|
{String method: "POST", bool write: true}) async {
|
2016-06-25 18:37:49 +00:00
|
|
|
HttpRequest request = new HttpRequest();
|
2016-11-28 03:28:41 +00:00
|
|
|
request.open(method, url);
|
2016-06-25 18:37:49 +00:00
|
|
|
request.responseType = "json";
|
|
|
|
request.setRequestHeader("Accept", "application/json");
|
2016-11-28 03:28:41 +00:00
|
|
|
if (write) request.setRequestHeader("Content-Type", "application/json");
|
|
|
|
if (app._authToken != null)
|
|
|
|
request.setRequestHeader("Authorization", "Bearer ${app._authToken}");
|
2016-06-25 18:37:49 +00:00
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<List> index([Map params]) async {
|
2016-11-28 03:28:41 +00:00
|
|
|
final request = await buildRequest('$basePath/${_buildQuery(params)}',
|
|
|
|
method: 'GET', write: false);
|
|
|
|
return await _send(request);
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future read(id, [Map params]) async {
|
2016-11-28 03:28:41 +00:00
|
|
|
final request = await buildRequest('$basePath/$id${_buildQuery(params)}',
|
|
|
|
method: 'GET', write: false);
|
|
|
|
return await _send(request);
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future create(data, [Map params]) async {
|
2016-11-28 03:28:41 +00:00
|
|
|
final request = await buildRequest("$basePath/${_buildQuery(params)}");
|
|
|
|
return await _send(request, _makeBody(data));
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future modify(id, data, [Map params]) async {
|
2016-11-28 03:28:41 +00:00
|
|
|
final request = await buildRequest("$basePath/$id${_buildQuery(params)}",
|
|
|
|
method: "PATCH");
|
|
|
|
return await _send(request, _makeBody(data));
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future update(id, data, [Map params]) async {
|
2016-11-28 03:28:41 +00:00
|
|
|
final request = await buildRequest("$basePath/$id${_buildQuery(params)}");
|
|
|
|
return await _send(request, _makeBody(data));
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future remove(id, [Map params]) async {
|
2016-11-28 03:28:41 +00:00
|
|
|
final request = await buildRequest("$basePath/$id${_buildQuery(params)}",
|
|
|
|
method: "DELETE");
|
|
|
|
return await _send(request);
|
2016-06-25 18:37:49 +00:00
|
|
|
}
|
|
|
|
}
|