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 * 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 * Migrated to work with Dart SDK 2.12.x Non NNBD
# 2.0.2 ## 2.0.2
* `_join` previously discarded quer parameters, etc. * `_join` previously discarded quer parameters, etc.
* Allow any `Map<String, dynamic>` as body, not just `Map<String, String>`. * 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. * Change `BaseAngelClient` constructor to accept `dynamic` instead of `String` for `baseUrl.
# 2.0.0 ## 2.0.0
* Deprecate `basePath` in favor of `baseUrl`. * Deprecate `basePath` in favor of `baseUrl`.
* `Angel` now extends `http.Client`. * `Angel` now extends `http.Client`.
* Deprecate `auth_types`. * Deprecate `auth_types`.
# 2.0.0-alpha.2 ## 2.0.0-alpha.2
* Make Service `index` always return `List<Data>`. * Make Service `index` always return `List<Data>`.
* Add `Service.map`. * Add `Service.map`.
# 2.0.0-alpha.1 ## 2.0.0-alpha.1
* Refactor `params` to `Map<String, dynamic>`. * Refactor `params` to `Map<String, dynamic>`.
# 2.0.0-alpha ## 2.0.0-alpha
* Depend on Dart 2. * Depend on Dart 2.
* Depend on Angel 2. * Depend on Angel 2.
* Remove `dart2_constant`. * Remove `dart2_constant`.
# 1.2.0+2 ## 1.2.0+2
* Code cleanup + housekeeping, update to `dart2_constant`, and * Code cleanup + housekeeping, update to `dart2_constant`, and
ensured build works with `2.0.0-dev.64.1`. 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. * 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. * `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. * `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. * `ServiceList` no longer ignores empty `index` events.
# 1.1.0+2 ## 1.1.0+2
* Updated `ServiceList` to also fire on `index`. * Updated `ServiceList` to also fire on `index`.
# 1.1.0+1 ## 1.1.0+1
* Added `ServiceList`. * Added `ServiceList`.

View file

@ -1,11 +1,14 @@
# angel3_client # Angel3 Client
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/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) [![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) [![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) [![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 ```dart
// Choose one or the other, depending on platform // 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 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!).
service on the server at the given path (say that five times fast!).
```dart ```dart
foo() async { 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; 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.
you can use the same class on the client and the server.
```dart ```dart
class Todo extends Model { class Todo extends Model {
@ -50,11 +51,12 @@ bar() async {
} }
``` ```
Just like on the server, services support `index`, `read`, `create`, `modify`, `update` and Just like on the server, services support `index`, `read`, `create`, `modify`, `update` and `remove`.
`remove`.
## Authentication ## Authentication
Local auth: Local auth:
```dart ```dart
var auth = await app.authenticate(type: 'local', credentials: {username: ..., password: ...}); var auth = await app.authenticate(type: 'local', credentials: {username: ..., password: ...});
print(auth.token); print(auth.token);
@ -62,6 +64,7 @@ print(auth.data); // User object
``` ```
Revive an existing jwt: Revive an existing jwt:
```dart ```dart
Future<AngelAuthResult> reviveJwt(String jwt) { Future<AngelAuthResult> reviveJwt(String jwt) {
return app.authenticate(credentials: {'token': jwt}); return app.authenticate(credentials: {'token': jwt});
@ -69,6 +72,7 @@ Future<AngelAuthResult> reviveJwt(String jwt) {
``` ```
Via Popup: Via Popup:
```dart ```dart
app.authenticateViaPopup('/auth/google').listen((jwt) { app.authenticateViaPopup('/auth/google').listen((jwt) {
// Do something with the JWT // Do something with the JWT
@ -76,6 +80,7 @@ app.authenticateViaPopup('/auth/google').listen((jwt) {
``` ```
Resume a session from localStorage (browser only): Resume a session from localStorage (browser only):
```dart ```dart
// Automatically checks for JSON-encoded 'token' in localStorage, // Automatically checks for JSON-encoded 'token' in localStorage,
// and tries to revive it // and tries to revive it
@ -83,13 +88,14 @@ await app.authenticate();
``` ```
Logout: Logout:
```dart ```dart
await app.logout(); await app.logout();
``` ```
# Live Updates ## Live Updates
Oftentimes, you will want to update a collection based on updates from a service.
Use `ServiceList` for this case: Oftentimes, you will want to update a collection based on updates from a service. Use `ServiceList` for this case:
```dart ```dart
build(BuildContext context) async { 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/request.dart' as http;
import 'package:http/src/response.dart' as http; import 'package:http/src/response.dart' as http;
import 'package:http/src/streamed_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'; import 'angel3_client.dart';
const Map<String, String> _readHeaders = {'Accept': 'application/json'}; const Map<String, String> _readHeaders = {'Accept': 'application/json'};
@ -16,8 +16,8 @@ const Map<String, String> _writeHeaders = {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}; };
Map<String, String>? _buildQuery(Map<String, dynamic>? params) { Map<String, String> _buildQuery(Map<String, dynamic>? params) {
return params?.map((k, v) => MapEntry(k, v.toString())); return params?.map((k, v) => MapEntry(k, v.toString())) ?? {};
} }
bool _invalid(http.Response response) => bool _invalid(http.Response response) =>
@ -50,7 +50,9 @@ abstract class BaseAngelClient extends Angel {
final StreamController<AngelAuthResult> _onAuthenticated = final StreamController<AngelAuthResult> _onAuthenticated =
StreamController<AngelAuthResult>(); StreamController<AngelAuthResult>();
final List<Service> _services = []; final List<Service> _services = [];
final http.BaseClient? client; final http.BaseClient client;
final _p = Context(style: Style.url);
@override @override
Stream<AngelAuthResult> get onAuthenticated => _onAuthenticated.stream; Stream<AngelAuthResult> get onAuthenticated => _onAuthenticated.stream;
@ -66,13 +68,12 @@ abstract class BaseAngelClient extends Angel {
type ??= 'token'; type ??= 'token';
var segments = baseUrl.pathSegments var segments = baseUrl.pathSegments
.followedBy(p.split(authEndpoint)) .followedBy(_p.split(authEndpoint))
.followedBy([type]); .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; http.Response response;
if (credentials != null) { if (credentials != null) {
@ -107,7 +108,7 @@ abstract class BaseAngelClient extends Angel {
@override @override
Future<void> close() async { Future<void> close() async {
client!.close(); client.close();
await _onAuthenticated.close(); await _onAuthenticated.close();
await Future.wait(_services.map((s) => s.close())).then((_) { await Future.wait(_services.map((s) => s.close())).then((_) {
_services.clear(); _services.clear();
@ -124,7 +125,7 @@ abstract class BaseAngelClient extends Angel {
if (authToken?.isNotEmpty == true) { if (authToken?.isNotEmpty == true) {
request.headers['authorization'] ??= 'Bearer $authToken'; request.headers['authorization'] ??= 'Bearer $authToken';
} }
return client!.send(request); return client.send(request);
} }
/// Sends a non-streaming [Request] and returns a non-streaming [Response]. /// Sends a non-streaming [Request] and returns a non-streaming [Response].
@ -157,7 +158,7 @@ abstract class BaseAngelClient extends Angel {
@override @override
Service<Id, Data> service<Id, Data>(String path, Service<Id, Data> service<Id, Data>(String path,
{Type? type, AngelDeserializer<Data>? deserializer}) { {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, var s = BaseAngelService<Id, Data>(client, this, url,
deserializer: deserializer); deserializer: deserializer);
_services.add(s); _services.add(s);
@ -167,7 +168,7 @@ abstract class BaseAngelClient extends Angel {
Uri _join(url) { Uri _join(url) {
var u = url is Uri ? url : Uri.parse(url.toString()); var u = url is Uri ? url : Uri.parse(url.toString());
if (u.hasScheme || u.hasAuthority) return u; 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 //@override
@ -208,9 +209,11 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
@override @override
final BaseAngelClient app; final BaseAngelClient app;
final Uri baseUrl; final Uri baseUrl;
final http.BaseClient? client; final http.BaseClient client;
final AngelDeserializer<Data>? deserializer; final AngelDeserializer<Data>? deserializer;
final _p = Context(style: Style.url);
final StreamController<List<Data?>> _onIndexed = StreamController(); final StreamController<List<Data?>> _onIndexed = StreamController();
final StreamController<Data?> _onRead = StreamController(), final StreamController<Data?> _onRead = StreamController(),
_onCreated = StreamController(), _onCreated = StreamController(),
@ -267,11 +270,11 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
request.headers['Authorization'] = 'Bearer ${app.authToken}'; request.headers['Authorization'] = 'Bearer ${app.authToken}';
} }
return client!.send(request); return client.send(request);
} }
@override @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 url = baseUrl.replace(queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('GET', url, _readHeaders); 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 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); _onIndexed.add(r);
return r; return r;
} catch (e, st) { } catch (e, st) {
@ -296,13 +306,15 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
} }
} }
return null; return [];
} }
@override @override
Future<Data?> read(id, [Map<String, dynamic>? params]) async { Future<Data?> read(id, [Map<String, dynamic>? params]) async {
var pa = _p.join(baseUrl.path, id.toString());
print(pa);
var url = baseUrl.replace( var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()), path: _p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params)); queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('GET', url, _readHeaders); var response = await app.sendUnstreamed('GET', url, _readHeaders);
@ -362,7 +374,7 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
@override @override
Future<Data?> modify(id, data, [Map<String, dynamic>? params]) async { Future<Data?> modify(id, data, [Map<String, dynamic>? params]) async {
var url = baseUrl.replace( var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()), path: _p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params)); queryParameters: _buildQuery(params));
var response = var response =
@ -394,7 +406,7 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
@override @override
Future<Data?> update(id, data, [Map<String, dynamic>? params]) async { Future<Data?> update(id, data, [Map<String, dynamic>? params]) async {
var url = baseUrl.replace( var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()), path: _p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params)); queryParameters: _buildQuery(params));
var response = var response =
@ -426,7 +438,7 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
@override @override
Future<Data?> remove(id, [Map<String, dynamic>? params]) async { Future<Data?> remove(id, [Map<String, dynamic>? params]) async {
var url = baseUrl.replace( var url = baseUrl.replace(
path: p.join(baseUrl.path, id.toString()), path: _p.join(baseUrl.path, id.toString()),
queryParameters: _buildQuery(params)); queryParameters: _buildQuery(params));
var response = await app.sendUnstreamed('DELETE', url, _readHeaders); 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> { class RestService<Id, Data> extends BaseAngelService<Id, Data> {
final Type? type; 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); : super(client, app, url);
@override @override

View file

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

View file

@ -12,7 +12,7 @@ class MockAngel extends BaseAngelClient {
@override @override
final SpecClient client = SpecClient(); final SpecClient client = SpecClient();
MockAngel() : super(null, 'http://localhost:3000'); MockAngel() : super(SpecClient(), 'http://localhost:3000');
@override @override
Stream<String> authenticateViaPopup(String url, 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_client/io.dart' as c;
import 'package:angel3_framework/angel3_framework.dart' as s; import 'package:angel3_framework/angel3_framework.dart' as s;
import 'package:angel3_framework/http.dart' as s; import 'package:angel3_framework/http.dart' as s;
import 'package:angel3_container/mirrors.dart';
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -13,11 +14,11 @@ void main() {
late StreamQueue queue; late StreamQueue queue;
setUp(() async { setUp(() async {
var serverApp = s.Angel(); var serverApp = s.Angel(reflector: MirrorsReflector());
var http = s.AngelHttp(serverApp); var http = s.AngelHttp(serverApp);
serverApp.use('/api/todos', s.MapService(autoIdAndDateFields: false)); serverApp.use('/api/todos', s.MapService(autoIdAndDateFields: false));
server = await http.startServer(); server = await http.startServer();
var uri = 'http://${server.address.address}:${server.port}'; var uri = 'http://${server.address.address}:${server.port}';
app = c.Rest(uri); app = c.Rest(uri);
list = c.ServiceList(app.service('api/todos')); 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_container/mirrors.dart';
import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart'; import 'package:angel3_framework/http.dart';