platform/lib/angel_client.dart

213 lines
5.9 KiB
Dart
Raw Normal View History

2016-06-24 00:25:11 +00:00
/// Client library for the Angel framework.
library angel_client;
import 'dart:async';
2017-12-10 05:13:31 +00:00
import 'package:collection/collection.dart';
2018-08-26 22:41:01 +00:00
import 'dart:convert';
2016-12-10 17:15:54 +00:00
import 'package:http/src/response.dart' as http;
2017-09-24 04:12:53 +00:00
export 'package:angel_http_exception/angel_http_exception.dart';
2016-06-24 19:02:35 +00:00
2016-06-24 21:06:57 +00:00
/// A function that configures an [Angel] client in some way.
typedef FutureOr AngelConfigurer(Angel app);
2016-06-24 19:02:35 +00:00
2016-12-13 16:35:35 +00:00
/// A function that deserializes data received from the server.
///
/// This is only really necessary in the browser, where `json_god`
/// doesn't work.
typedef T AngelDeserializer<T>(x);
2016-12-13 16:35:35 +00:00
2016-06-24 19:02:35 +00:00
/// Represents an Angel server that we are querying.
abstract class Angel {
2017-02-28 21:56:59 +00:00
String authToken;
2016-06-24 19:02:35 +00:00
String basePath;
Angel(String this.basePath);
2016-06-24 21:06:57 +00:00
2017-06-03 17:43:01 +00:00
/// Fired whenever a WebSocket is successfully authenticated.
Stream<AngelAuthResult> get onAuthenticated;
2016-11-29 00:42:02 +00:00
Future<AngelAuthResult> authenticate(
2016-12-03 18:21:44 +00:00
{String type,
2016-11-29 00:42:02 +00:00
credentials,
String authEndpoint: '/auth',
String reviveEndpoint: '/auth/token'});
2016-11-28 03:28:41 +00:00
2017-02-28 21:56:59 +00:00
/// Opens the [url] in a new window, and returns a [Stream] that will fire a JWT on successful authentication.
Stream<String> authenticateViaPopup(String url, {String eventName: 'token'});
2016-12-10 14:50:05 +00:00
Future close();
2016-06-24 21:06:57 +00:00
/// Applies an [AngelConfigurer] to this instance.
Future configure(AngelConfigurer configurer) async {
await configurer(this);
}
2017-03-29 01:52:19 +00:00
/// Logs the current user out of the application.
Future logout();
Service<Id, Data> service<Id, Data>(String path,
{Type type, AngelDeserializer<Data> deserializer});
2016-12-10 17:15:54 +00:00
2017-02-28 21:56:59 +00:00
Future<http.Response> delete(String url, {Map<String, String> headers});
2016-12-10 17:15:54 +00:00
Future<http.Response> get(String url, {Map<String, String> headers});
Future<http.Response> head(String url, {Map<String, String> headers});
2017-02-28 21:56:59 +00:00
Future<http.Response> patch(String url, {body, Map<String, String> headers});
2016-12-10 17:15:54 +00:00
2017-02-28 21:56:59 +00:00
Future<http.Response> post(String url, {body, Map<String, String> headers});
2016-12-10 17:15:54 +00:00
2017-02-28 21:56:59 +00:00
Future<http.Response> put(String url, {body, Map<String, String> headers});
2016-06-24 19:02:35 +00:00
}
2016-06-24 00:25:11 +00:00
2016-11-28 03:28:41 +00:00
/// Represents the result of authentication with an Angel server.
2016-12-09 00:24:07 +00:00
class AngelAuthResult {
String _token;
final Map<String, dynamic> data = {};
String get token => _token;
AngelAuthResult({String token, Map<String, dynamic> data: const {}}) {
_token = token;
this.data.addAll(data ?? {});
}
factory AngelAuthResult.fromMap(Map data) {
final result = new AngelAuthResult();
if (data is Map && data.containsKey('token') && data['token'] is String)
2018-08-26 22:41:01 +00:00
result._token = data['token'].toString();
2016-12-09 00:24:07 +00:00
2018-08-26 22:41:01 +00:00
if (data is Map)
result.data.addAll((data['data'] as Map<String, dynamic>) ?? {});
2016-12-03 18:21:44 +00:00
2016-12-09 00:24:07 +00:00
return result;
}
2018-06-23 00:18:38 +00:00
factory AngelAuthResult.fromJson(String s) =>
2018-08-26 22:41:01 +00:00
new AngelAuthResult.fromMap(json.decode(s) as Map);
2016-12-09 00:24:07 +00:00
Map<String, dynamic> toJson() {
return {'token': token, 'data': data};
}
2016-11-28 03:28:41 +00:00
}
2016-06-24 00:25:11 +00:00
/// Queries a service on an Angel server, with the same API.
abstract class Service<Id, Data> {
2017-06-03 17:43:01 +00:00
/// Fired on `indexed` events.
2017-09-24 04:15:40 +00:00
Stream get onIndexed;
2017-06-03 17:43:01 +00:00
/// Fired on `read` events.
Stream<Data> get onRead;
2017-06-03 17:43:01 +00:00
/// Fired on `created` events.
Stream<Data> get onCreated;
2017-06-03 17:43:01 +00:00
/// Fired on `modified` events.
Stream<Data> get onModified;
2017-06-03 17:43:01 +00:00
/// Fired on `updated` events.
Stream<Data> get onUpdated;
2017-06-03 17:43:01 +00:00
/// Fired on `removed` events.
Stream<Data> get onRemoved;
2017-06-03 17:43:01 +00:00
2016-06-24 21:06:57 +00:00
/// The Angel instance powering this service.
2016-11-28 03:28:41 +00:00
Angel get app;
2016-06-24 21:06:57 +00:00
2017-06-03 17:43:01 +00:00
Future close();
2016-06-24 00:25:11 +00:00
/// Retrieves all resources.
Future index([Map<String, dynamic> params]);
2016-06-24 00:25:11 +00:00
/// Retrieves the desired resource.
Future read(Id id, [Map<String, dynamic> params]);
2016-06-24 00:25:11 +00:00
/// Creates a resource.
Future create(Data data, [Map<String, dynamic> params]);
2016-06-24 00:25:11 +00:00
/// Modifies a resource.
Future modify(Id id, Data data, [Map<String, dynamic> params]);
2016-06-24 00:25:11 +00:00
/// Overwrites a resource.
Future update(Id id, Data data, [Map<String, dynamic> params]);
2016-06-24 00:25:11 +00:00
/// Removes the given resource.
Future remove(Id id, [Map<String, dynamic> params]);
2016-09-03 12:02:32 +00:00
}
2017-12-10 05:13:31 +00:00
/// A [List] that automatically updates itself whenever the referenced [service] fires an event.
class ServiceList<Id, Data> extends DelegatingList {
2017-12-10 05:13:31 +00:00
/// A field name used to compare [Map] by ID.
final String idField;
/// If `true` (default: `false`), then `index` events will be handled as a [Map] containing a `data` field.
///
/// See https://github.com/angel-dart/paginate.
final bool asPaginated;
2017-12-21 20:08:45 +00:00
/// A function used to compare the ID's two items for equality.
///
/// Defaults to comparing the [idField] of `Map` instances.
final Equality _compare;
2017-12-10 05:13:31 +00:00
final Service<Id, Data> service;
2017-12-10 05:13:31 +00:00
final StreamController<ServiceList<Id, Data>> _onChange =
new StreamController();
2017-12-10 05:13:31 +00:00
final List<StreamSubscription> _subs = [];
2017-12-13 06:35:08 +00:00
ServiceList(this.service,
2017-12-21 20:08:45 +00:00
{this.idField, this.asPaginated: false, Equality compare})
: _compare = compare ?? new EqualityBy((map) => map[idField ?? 'id']),
super([]) {
2017-12-10 05:13:31 +00:00
// Index
_subs.add(service.onIndexed.listen((data) {
var items = asPaginated == true ? data['data'] : data;
2017-12-13 16:31:06 +00:00
this
..clear()
2018-08-26 22:41:01 +00:00
..addAll(items as Iterable);
2017-12-13 16:31:06 +00:00
_onChange.add(this);
2017-12-10 05:13:31 +00:00
}));
// Created
_subs.add(service.onCreated.listen((item) {
add(item);
_onChange.add(this);
}));
// Modified/Updated
handleModified(Data item) {
2017-12-10 05:13:31 +00:00
var indices = <int>[];
for (int i = 0; i < length; i++) {
2017-12-21 20:08:45 +00:00
if (_compare.equals(item, this[i])) indices.add(i);
2017-12-10 05:13:31 +00:00
}
if (indices.isNotEmpty) {
2017-12-13 06:35:08 +00:00
for (var i in indices) this[i] = item;
2017-12-10 05:13:31 +00:00
_onChange.add(this);
}
}
_subs.addAll([
service.onModified.listen(handleModified),
service.onUpdated.listen(handleModified),
]);
// Removed
_subs.add(service.onRemoved.listen((item) {
2017-12-21 20:08:45 +00:00
removeWhere((x) => _compare.equals(item, x));
2017-12-10 05:13:31 +00:00
_onChange.add(this);
}));
}
/// Fires whenever the underlying [service] fires a change event.
Stream<ServiceList<Id, Data>> get onChange => _onChange.stream;
2017-12-10 05:13:31 +00:00
Future close() async {
_onChange.close();
}
}