Fixed client

This commit is contained in:
thomashii 2021-07-09 22:19:16 +08:00
parent d9d6ef1fcb
commit c16b369de8
8 changed files with 98 additions and 59 deletions

View file

@ -1,51 +1,73 @@
# Change Log
## 4.0.1
* Updated README
* Fixed NNBD issues
* Fixed path issue on Windows
* All 13 unit tests passed
## 4.0.0
# 4.0.0
* Migrated to support Dart SDK 2.12.x NNBD
# 3.0.0
## 3.0.0
* Migrated to work with Dart SDK 2.12.x Non NNBD
# 2.0.2
## 2.0.2
* `_join` previously discarded quer parameters, etc.
* Allow any `Map<String, dynamic>` as body, not just `Map<String, String>`.
# 2.0.1
## 2.0.1
* Change `BaseAngelClient` constructor to accept `dynamic` instead of `String` for `baseUrl.
# 2.0.0
## 2.0.0
* Deprecate `basePath` in favor of `baseUrl`.
* `Angel` now extends `http.Client`.
* Deprecate `auth_types`.
# 2.0.0-alpha.2
## 2.0.0-alpha.2
* Make Service `index` always return `List<Data>`.
* Add `Service.map`.
# 2.0.0-alpha.1
## 2.0.0-alpha.1
* Refactor `params` to `Map<String, dynamic>`.
# 2.0.0-alpha
## 2.0.0-alpha
* Depend on Dart 2.
* Depend on Angel 2.
* Remove `dart2_constant`.
# 1.2.0+2
## 1.2.0+2
* Code cleanup + housekeeping, update to `dart2_constant`, and
ensured build works with `2.0.0-dev.64.1`.
# 1.2.0+1
## 1.2.0+1
* Removed a type annotation in `authenticateViaPopup` to prevent breaking with DDC.
# 1.2.0
## 1.2.0
* `ServiceList` now uses `Equality` from `package:collection` to compare items.
* `Service`s will now add service errors to corresponding streams if there is a listener.
# 1.1.0+3
## 1.1.0+3
* `ServiceList` no longer ignores empty `index` events.
# 1.1.0+2
## 1.1.0+2
* Updated `ServiceList` to also fire on `index`.
# 1.1.0+1
## 1.1.0+1
* Added `ServiceList`.

View file

@ -1,11 +1,14 @@
# angel3_client
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_client)
# Angel3 Client
[![version](https://img.shields.io/badge/pub-v4.0.1-brightgreen)](https://pub.dartlang.org/packages/angel3_client)
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/client/LICENSE)
# Usage
A browser, mobile and command line based client that supports querying Angel3 servers
## Usage
```dart
// Choose one or the other, depending on platform
@ -18,8 +21,7 @@ main() async {
}
```
You can call `service` to receive an instance of `Service`, which acts as a client to a
service on the server at the given path (say that five times fast!).
You can call `service` to receive an instance of `Service`, which acts as a client to a service on the server at the given path (say that five times fast!).
```dart
foo() async {
@ -30,8 +32,7 @@ foo() async {
}
```
The CLI client also supports reflection via `angel3_json_god`. There is no need to work with Maps;
you can use the same class on the client and the server.
The CLI client also supports reflection via `angel3_json_god`. There is no need to work with Maps; you can use the same class on the client and the server.
```dart
class Todo extends Model {
@ -50,11 +51,12 @@ bar() async {
}
```
Just like on the server, services support `index`, `read`, `create`, `modify`, `update` and
`remove`.
Just like on the server, services support `index`, `read`, `create`, `modify`, `update` and `remove`.
## Authentication
Local auth:
```dart
var auth = await app.authenticate(type: 'local', credentials: {username: ..., password: ...});
print(auth.token);
@ -62,6 +64,7 @@ print(auth.data); // User object
```
Revive an existing jwt:
```dart
Future<AngelAuthResult> reviveJwt(String jwt) {
return app.authenticate(credentials: {'token': jwt});
@ -69,6 +72,7 @@ Future<AngelAuthResult> reviveJwt(String jwt) {
```
Via Popup:
```dart
app.authenticateViaPopup('/auth/google').listen((jwt) {
// Do something with the JWT
@ -76,6 +80,7 @@ app.authenticateViaPopup('/auth/google').listen((jwt) {
```
Resume a session from localStorage (browser only):
```dart
// Automatically checks for JSON-encoded 'token' in localStorage,
// and tries to revive it
@ -83,13 +88,14 @@ await app.authenticate();
```
Logout:
```dart
await app.logout();
```
# Live Updates
Oftentimes, you will want to update a collection based on updates from a service.
Use `ServiceList` for this case:
## Live Updates
Oftentimes, you will want to update a collection based on updates from a service. Use `ServiceList` for this case:
```dart
build(BuildContext context) async {

View file

@ -7,7 +7,7 @@ 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;
import 'package:path/path.dart' as p;
import 'package:path/path.dart';
import 'angel3_client.dart';
const Map<String, String> _readHeaders = {'Accept': 'application/json'};
@ -16,8 +16,8 @@ const Map<String, String> _writeHeaders = {
'Content-Type': 'application/json'
};
Map<String, String>? _buildQuery(Map<String, dynamic>? params) {
return params?.map((k, v) => MapEntry(k, v.toString()));
Map<String, String> _buildQuery(Map<String, dynamic>? params) {
return params?.map((k, v) => MapEntry(k, v.toString())) ?? {};
}
bool _invalid(http.Response response) =>
@ -50,7 +50,9 @@ abstract class BaseAngelClient extends Angel {
final StreamController<AngelAuthResult> _onAuthenticated =
StreamController<AngelAuthResult>();
final List<Service> _services = [];
final http.BaseClient? client;
final http.BaseClient client;
final _p = Context(style: Style.url);
@override
Stream<AngelAuthResult> get onAuthenticated => _onAuthenticated.stream;
@ -66,13 +68,12 @@ abstract class BaseAngelClient extends Angel {
type ??= 'token';
var segments = baseUrl.pathSegments
.followedBy(p.split(authEndpoint))
.followedBy(_p.split(authEndpoint))
.followedBy([type]);
// TODO: convert windows path to proper url
var p1 = p.joinAll(segments).replaceAll('\\', '/');
//var p1 = p.joinAll(segments).replaceAll('\\', '/');
var url = baseUrl.replace(path: p1);
var url = baseUrl.replace(path: _p.joinAll(segments));
http.Response response;
if (credentials != null) {
@ -107,7 +108,7 @@ abstract class BaseAngelClient extends Angel {
@override
Future<void> close() async {
client!.close();
client.close();
await _onAuthenticated.close();
await Future.wait(_services.map((s) => s.close())).then((_) {
_services.clear();
@ -124,7 +125,7 @@ abstract class BaseAngelClient extends Angel {
if (authToken?.isNotEmpty == true) {
request.headers['authorization'] ??= 'Bearer $authToken';
}
return client!.send(request);
return client.send(request);
}
/// Sends a non-streaming [Request] and returns a non-streaming [Response].
@ -157,7 +158,7 @@ abstract class BaseAngelClient extends Angel {
@override
Service<Id, Data> service<Id, Data>(String path,
{Type? type, AngelDeserializer<Data>? deserializer}) {
var url = baseUrl.replace(path: p.join(baseUrl.path, path));
var url = baseUrl.replace(path: _p.join(baseUrl.path, path));
var s = BaseAngelService<Id, Data>(client, this, url,
deserializer: deserializer);
_services.add(s);
@ -167,7 +168,7 @@ abstract class BaseAngelClient extends Angel {
Uri _join(url) {
var u = url is Uri ? url : Uri.parse(url.toString());
if (u.hasScheme || u.hasAuthority) return u;
return u.replace(path: p.join(baseUrl.path, u.path));
return u.replace(path: _p.join(baseUrl.path, u.path));
}
//@override
@ -208,9 +209,11 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
@override
final BaseAngelClient app;
final Uri baseUrl;
final http.BaseClient? client;
final http.BaseClient client;
final AngelDeserializer<Data>? deserializer;
final _p = Context(style: Style.url);
final StreamController<List<Data?>> _onIndexed = StreamController();
final StreamController<Data?> _onRead = StreamController(),
_onCreated = StreamController(),
@ -267,11 +270,11 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
request.headers['Authorization'] = 'Bearer ${app.authToken}';
}
return client!.send(request);
return client.send(request);
}
@override
Future<List<Data?>?> index([Map<String, dynamic>? params]) async {
Future<List<Data>> index([Map<String, dynamic>? params]) async {
var url = baseUrl.replace(queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('GET', url, _readHeaders);
@ -285,7 +288,14 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
}
var v = json.decode(response.body) as List;
var r = v.map(deserialize).toList();
//var r = v.map(deserialize).toList();
var r = <Data>[];
v.forEach((element) {
var a = deserialize(element);
if (a != null) {
r.add(a);
}
});
_onIndexed.add(r);
return r;
} catch (e, st) {
@ -296,13 +306,15 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
}
}
return null;
return [];
}
@override
Future<Data?> read(id, [Map<String, dynamic>? params]) async {
var pa = _p.join(baseUrl.path, id.toString());
print(pa);
var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()),
path: _p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('GET', url, _readHeaders);
@ -362,7 +374,7 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
@override
Future<Data?> modify(id, data, [Map<String, dynamic>? params]) async {
var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()),
path: _p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params));
var response =
@ -394,7 +406,7 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
@override
Future<Data?> update(id, data, [Map<String, dynamic>? params]) async {
var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()),
path: _p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params));
var response =
@ -426,7 +438,7 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
@override
Future<Data?> remove(id, [Map<String, dynamic>? params]) async {
var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()),
path: _p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('DELETE', url, _readHeaders);

View file

@ -44,7 +44,7 @@ class Rest extends BaseAngelClient {
class RestService<Id, Data> extends BaseAngelService<Id, Data> {
final Type? type;
RestService(http.BaseClient? client, BaseAngelClient app, url, this.type)
RestService(http.BaseClient client, BaseAngelClient app, url, this.type)
: super(client, app, url);
@override

View file

@ -1,7 +1,8 @@
name: angel3_client
version: 4.0.0
description: Support for querying Angel servers in the browser, Flutter, and command-line.
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/client
version: 4.0.1
description: A browser, mobile and command line based client that supports querying Angel3 servers
homepage: https://angel3-framework.web.app/
respository: https://github.com/dukefirehawk/angel/tree/angel3/packages/client
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
@ -9,7 +10,6 @@ dependencies:
angel3_json_god: ^4.0.0
collection: ^1.15.0
http: ^0.13.1
#dart_json_mapper: ^1.7.0
meta: ^1.3.0
path: ^1.8.0
dev_dependencies:

View file

@ -12,7 +12,7 @@ class MockAngel extends BaseAngelClient {
@override
final SpecClient client = SpecClient();
MockAngel() : super(null, 'http://localhost:3000');
MockAngel() : super(SpecClient(), 'http://localhost:3000');
@override
Stream<String> authenticateViaPopup(String url,

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:angel3_client/io.dart' as c;
import 'package:angel3_framework/angel3_framework.dart' as s;
import 'package:angel3_framework/http.dart' as s;
import 'package:angel3_container/mirrors.dart';
import 'package:pedantic/pedantic.dart';
import 'package:test/test.dart';
@ -13,11 +14,11 @@ void main() {
late StreamQueue queue;
setUp(() async {
var serverApp = s.Angel();
var serverApp = s.Angel(reflector: MirrorsReflector());
var http = s.AngelHttp(serverApp);
serverApp.use('/api/todos', s.MapService(autoIdAndDateFields: false));
server = await http.startServer();
var uri = 'http://${server.address.address}:${server.port}';
app = c.Rest(uri);
list = c.ServiceList(app.service('api/todos'));

View file

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:angel3_container/mirrors.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart';