From 7a2a4e75529c7aa9e87861183824f2d2ee36f754 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 23 Jun 2016 15:13:52 -0400 Subject: [PATCH 01/69] Initial commit --- .gitignore | 27 +++++++++++++++++++++++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 3 files changed, 50 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7c280441 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.buildlog +.packages +.project +.pub/ +build/ +**/packages/ + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +pubspec.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..eb4ce33e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 angel-dart + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..2891d08d --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# angel_client +Client library for the Angel framework. From 98838cd8de6b4cdd726d27e9cfe856df1a63e00b Mon Sep 17 00:00:00 2001 From: regiostech Date: Thu, 23 Jun 2016 20:25:11 -0400 Subject: [PATCH 02/69] :) --- .gitignore | 1 + lib/angel_client.dart | 26 ++++++++++++++ lib/src/rest.dart | 80 +++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 12 +++++++ 4 files changed, 119 insertions(+) create mode 100644 lib/angel_client.dart create mode 100644 lib/src/rest.dart create mode 100644 pubspec.yaml diff --git a/.gitignore b/.gitignore index 7c280441..ea89ccf0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ doc/api/ # Don't commit pubspec lock file # (Library packages only! Remove pattern if developing an application package) pubspec.lock +.idea \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart new file mode 100644 index 00000000..7982440c --- /dev/null +++ b/lib/angel_client.dart @@ -0,0 +1,26 @@ +/// Client library for the Angel framework. +library angel_client; + +import 'dart:async'; +export 'src/rest.dart'; + +/// Queries a service on an Angel server, with the same API. +abstract class Service { + /// Retrieves all resources. + Future index([Map params]); + + /// Retrieves the desired resource. + Future read(id, [Map params]); + + /// Creates a resource. + Future create(data, [Map params]); + + /// Modifies a resource. + Future modify(id, data, [Map params]); + + /// Overwrites a resource. + Future update(id, data, [Map params]); + + /// Removes the given resource. + Future remove(id, [Map params]); +} \ No newline at end of file diff --git a/lib/src/rest.dart b/lib/src/rest.dart new file mode 100644 index 00000000..e1144be7 --- /dev/null +++ b/lib/src/rest.dart @@ -0,0 +1,80 @@ +library angel_client.rest; + +import 'dart:async'; +import 'dart:convert' show JSON; +import 'package:http/http.dart'; +import '../angel_client.dart'; + +_buildQuery(Map params) { + if (params == null || params == {}) + return ""; + + String result = ""; + return result; +} + +const Map _readHeaders = const { + "Accept": "application/json" +}; + +const Map _writeHeaders = const { + "Accept": "application/json", + "Content-Type": "application/json" +}; + +/// Queries an Angel service via REST. +class RestService extends Service { + String basePath; + BaseClient client; + + RestService(Pattern path, BaseClient this.client) { + this.basePath = (path is RegExp) ? path.pattern : path; + } + + @override + Future index([Map params]) async { + var response = await client.get( + "$basePath/${_buildQuery(params)}", headers: _readHeaders); + return JSON.decode(response.body); + } + + @override + Future read(id, [Map params]) async { + var response = await client.get( + "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); + return JSON.decode(response.body); + } + + @override + Future create(data, [Map params]) async { + var response = await client.post( + "$basePath/${_buildQuery(params)}", body: JSON.encode(data), + headers: _writeHeaders); + return JSON.decode(response.body); + } + + @override + Future modify(id, data, [Map params]) async { + var response = await client.patch( + "$basePath/$id${_buildQuery(params)}", body: JSON.encode(data), + headers: _writeHeaders); + return JSON.decode(response.body); + } + + @override + Future update(id, data, [Map params]) async { + var response = await client.patch( + "$basePath/$id${_buildQuery(params)}", body: JSON.encode(data), + headers: _writeHeaders); + return JSON.decode(response.body); + } + + @override + Future remove(id, [Map params]) async { + var response = await client.delete( + "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); + return JSON.decode(response.body); + } + + +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..56c70487 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,12 @@ +name: angel_client +version: 1.0.0-dev +description: Client library for the Angel framework. +author: Tobe O +homepage: https://github.com/angel-dart/angel_client +dependencies: + json_god: ">=2.0.0-beta <3.0.0" + merge_map: ">=1.0.0 <2.0.0" +dev_dependencies: + angel_framework: ">=1.0.0-dev <2.0.0" + http: ">= 0.11.3 < 0.12.0" + test: ">= 0.12.13 < 0.13.0" \ No newline at end of file From d588411d6785fd7ed045353b1c60f6bde4640a6d Mon Sep 17 00:00:00 2001 From: thosakwe Date: Fri, 24 Jun 2016 15:02:35 -0400 Subject: [PATCH 03/69] More --- lib/angel_client.dart | 13 ++++++++++++- lib/{src => }/rest.dart | 21 ++++++++++++++------- test/packages | 1 + test/rest.dart | 37 +++++++++++++++++++++++++++++++++++++ test/shared.dart | 6 ++++++ 5 files changed, 70 insertions(+), 8 deletions(-) rename lib/{src => }/rest.dart (83%) create mode 120000 test/packages create mode 100644 test/rest.dart create mode 100644 test/shared.dart diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 7982440c..6a73fde3 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -2,7 +2,18 @@ library angel_client; import 'dart:async'; -export 'src/rest.dart'; +import 'dart:convert' show JSON; +import 'package:http/http.dart'; +part 'rest.dart'; + + +/// Represents an Angel server that we are querying. +abstract class Angel { + String basePath; + + Angel(String this.basePath); + Service service(Pattern path); +} /// Queries a service on an Angel server, with the same API. abstract class Service { diff --git a/lib/src/rest.dart b/lib/rest.dart similarity index 83% rename from lib/src/rest.dart rename to lib/rest.dart index e1144be7..e68c3424 100644 --- a/lib/src/rest.dart +++ b/lib/rest.dart @@ -1,9 +1,4 @@ -library angel_client.rest; - -import 'dart:async'; -import 'dart:convert' show JSON; -import 'package:http/http.dart'; -import '../angel_client.dart'; +part of angel_client; _buildQuery(Map params) { if (params == null || params == {}) @@ -22,12 +17,24 @@ const Map _writeHeaders = const { "Content-Type": "application/json" }; +class Rest extends Angel { + BaseClient client; + + Rest(String path, BaseClient this.client):super(path); + + @override + RestService service(String path) { + String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), ""); + return new RestService._base("$basePath/$uri", client); + } +} + /// Queries an Angel service via REST. class RestService extends Service { String basePath; BaseClient client; - RestService(Pattern path, BaseClient this.client) { + RestService._base(Pattern path, BaseClient this.client) { this.basePath = (path is RegExp) ? path.pattern : path; } diff --git a/test/packages b/test/packages new file mode 120000 index 00000000..a16c4050 --- /dev/null +++ b/test/packages @@ -0,0 +1 @@ +../packages \ No newline at end of file diff --git a/test/rest.dart b/test/rest.dart new file mode 100644 index 00000000..055e65a3 --- /dev/null +++ b/test/rest.dart @@ -0,0 +1,37 @@ +import 'dart:io'; +import 'package:angel_client/angel_client.dart' as client; +import 'package:angel_framework/angel_framework.dart' as server; +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; +import 'shared.dart'; + +main() { + group("rest", () { + server.Angel serverApp = new server.Angel(); + server.HookedService serverPostcards; + client.Angel clientApp; + client.Service clientPostcards; + HttpServer httpServer; + + setUp(() async { + httpServer = + await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); + serverApp.use("/postcards", new server.MemoryService()); + serverPostcards = serverApp.service("postcards"); + + clientApp = new client.Rest("http://localhost:3000", new http.Client()); + clientPostcards = clientApp.service("postcards"); + }); + + tearDown(() async { + await httpServer.close(force: true); + }); + + test("index", () async { + Postcard niagaraFalls = await serverPostcards.create( + new Postcard(location: "Niagara Falls", message: "Missing you!")); + List indexed = await clientPostcards.index(); + print(indexed); + }); + }); +} diff --git a/test/shared.dart b/test/shared.dart new file mode 100644 index 00000000..4a8d2052 --- /dev/null +++ b/test/shared.dart @@ -0,0 +1,6 @@ +class Postcard { + String location; + String message; + + Postcard({String this.location, String this.message}); +} \ No newline at end of file From 65254902c0b203067194f316d3bfb45e425e5cec Mon Sep 17 00:00:00 2001 From: regiostech Date: Fri, 24 Jun 2016 17:06:57 -0400 Subject: [PATCH 04/69] Done --- README.md | 49 +++++++++++++++++++++ lib/angel_client.dart | 15 ++++++- lib/rest.dart | 44 ++++++++++++------- pubspec.yaml | 2 +- test/packages | 1 - test/rest.dart | 100 +++++++++++++++++++++++++++++++++++++++++- test/shared.dart | 22 ++++++++-- 7 files changed, 209 insertions(+), 24 deletions(-) delete mode 120000 test/packages diff --git a/README.md b/README.md index 2891d08d..f90b691c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,51 @@ # angel_client Client library for the Angel framework. + +# Isomorphic +The REST client depends on `http`, because it can run in the browser or on the command-line. +Depending on your environment, you must pass an instance of `BaseClient` to the constructor. + +# Usage +This library provides the same API as an Angel server. + +```dart +import 'package:angel_client/angel_client.dart'; +import 'package:http/browser_client.dart'; + +main() async { + Angel app = new Rest("http://localhost:3000", new BrowserClient()); +} +``` + +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 { + Service Todos = app.service("todos"); + List todos = await Todos.index(); + + print(todos.length); +} +``` + +The REST client also supports reflection via `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 { + String text; + + Todo({String this.text}); +} + +bar() async { + Service Todos = app.service("todos", type: Todo); + List todos = await Todos.index(); + + print(todos.length); +} +``` + +Just like on the server, services support `index`, `read`, `create`, `modify`, `update` and +`remove`. \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 6a73fde3..ac8a8d3e 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -4,19 +4,32 @@ library angel_client; import 'dart:async'; import 'dart:convert' show JSON; import 'package:http/http.dart'; +import 'package:json_god/json_god.dart' as god; + part 'rest.dart'; +/// A function that configures an [Angel] client in some way. +typedef Future AngelConfigurer(Angel app); /// Represents an Angel server that we are querying. abstract class Angel { String basePath; Angel(String this.basePath); - Service service(Pattern path); + + /// Applies an [AngelConfigurer] to this instance. + Future configure(AngelConfigurer configurer) async { + await configurer(this); + } + + Service service(Pattern path, {Type type}); } /// Queries a service on an Angel server, with the same API. abstract class Service { + /// The Angel instance powering this service. + Angel app; + /// Retrieves all resources. Future index([Map params]); diff --git a/lib/rest.dart b/lib/rest.dart index e68c3424..072fda44 100644 --- a/lib/rest.dart +++ b/lib/rest.dart @@ -20,12 +20,13 @@ const Map _writeHeaders = const { class Rest extends Angel { BaseClient client; - Rest(String path, BaseClient this.client):super(path); + Rest(String path, BaseClient this.client) :super(path); @override - RestService service(String path) { + RestService service(String path, {Type type}) { String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), ""); - return new RestService._base("$basePath/$uri", client); + return new RestService._base("$basePath/$uri", client, type) + ..app = this; } } @@ -33,55 +34,68 @@ class Rest extends Angel { class RestService extends Service { String basePath; BaseClient client; + Type outputType; - RestService._base(Pattern path, BaseClient this.client) { + RestService._base(Pattern path, BaseClient this.client, + Type this.outputType) { this.basePath = (path is RegExp) ? path.pattern : path; } + _makeBody(data) { + if (outputType == null) + return JSON.encode(data); + else return god.serialize(data); + } + @override Future index([Map params]) async { var response = await client.get( "$basePath/${_buildQuery(params)}", headers: _readHeaders); - return JSON.decode(response.body); + + if (outputType == null) + return god.deserialize(response.body); + + else { + return JSON.decode(response.body).map((x) => + god.deserializeDatum(x, outputType: outputType)).toList(); + } } @override Future read(id, [Map params]) async { var response = await client.get( "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); - return JSON.decode(response.body); + return god.deserialize(response.body, outputType: outputType); } @override Future create(data, [Map params]) async { var response = await client.post( - "$basePath/${_buildQuery(params)}", body: JSON.encode(data), + "$basePath/${_buildQuery(params)}", body: _makeBody(data), headers: _writeHeaders); - return JSON.decode(response.body); + return god.deserialize(response.body, outputType: outputType); } @override Future modify(id, data, [Map params]) async { var response = await client.patch( - "$basePath/$id${_buildQuery(params)}", body: JSON.encode(data), + "$basePath/$id${_buildQuery(params)}", body: _makeBody(data), headers: _writeHeaders); - return JSON.decode(response.body); + return god.deserialize(response.body, outputType: outputType); } @override Future update(id, data, [Map params]) async { var response = await client.patch( - "$basePath/$id${_buildQuery(params)}", body: JSON.encode(data), + "$basePath/$id${_buildQuery(params)}", body: _makeBody(data), headers: _writeHeaders); - return JSON.decode(response.body); + return god.deserialize(response.body, outputType: outputType); } @override Future remove(id, [Map params]) async { var response = await client.delete( "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); - return JSON.decode(response.body); + return god.deserialize(response.body, outputType: outputType); } - - } diff --git a/pubspec.yaml b/pubspec.yaml index 56c70487..e303f079 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,6 +7,6 @@ dependencies: json_god: ">=2.0.0-beta <3.0.0" merge_map: ">=1.0.0 <2.0.0" dev_dependencies: - angel_framework: ">=1.0.0-dev <2.0.0" + angel_framework: 1.0.0-dev+5 http: ">= 0.11.3 < 0.12.0" test: ">= 0.12.13 < 0.13.0" \ No newline at end of file diff --git a/test/packages b/test/packages deleted file mode 120000 index a16c4050..00000000 --- a/test/packages +++ /dev/null @@ -1 +0,0 @@ -../packages \ No newline at end of file diff --git a/test/rest.dart b/test/rest.dart index 055e65a3..0bcdb6b8 100644 --- a/test/rest.dart +++ b/test/rest.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:angel_client/angel_client.dart' as client; import 'package:angel_framework/angel_framework.dart' as server; import 'package:http/http.dart' as http; +import 'package:json_god/json_god.dart' as god; import 'package:test/test.dart'; import 'shared.dart'; @@ -11,16 +12,18 @@ main() { server.HookedService serverPostcards; client.Angel clientApp; client.Service clientPostcards; + client.Service clientTypedPostcards; HttpServer httpServer; setUp(() async { httpServer = - await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); + await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); serverApp.use("/postcards", new server.MemoryService()); serverPostcards = serverApp.service("postcards"); clientApp = new client.Rest("http://localhost:3000", new http.Client()); clientPostcards = clientApp.service("postcards"); + clientTypedPostcards = clientApp.service("postcards", type: Postcard); }); tearDown(() async { @@ -32,6 +35,99 @@ main() { new Postcard(location: "Niagara Falls", message: "Missing you!")); List indexed = await clientPostcards.index(); print(indexed); + + expect(indexed.length, equals(1)); + expect(indexed[0].keys.length, equals(3)); + expect(indexed[0]['id'], equals(niagaraFalls.id)); + expect(indexed[0]['location'], equals(niagaraFalls.location)); + expect(indexed[0]['message'], equals(niagaraFalls.message)); + + Postcard louvre = await serverPostcards.create(new Postcard( + location: "The Louvre", message: "The Mona Lisa was watching me!")); + print(god.serialize(louvre)); + List typedIndexed = await clientTypedPostcards.index(); + expect(typedIndexed.length, equals(2)); + expect(typedIndexed[1], equals(louvre)); + }); + + test("create/read", () async { + Map opry = {"location": "Grand Ole Opry", "message": "Yeehaw!"}; + var created = await clientPostcards.create(opry); + print(created); + + expect(created['id'] == null, equals(false)); + expect(created["location"], equals(opry["location"])); + expect(created["message"], equals(opry["message"])); + + var read = await clientPostcards.read(created['id']); + print(read); + expect(read['id'], equals(created['id'])); + expect(read['location'], equals(created['location'])); + expect(read['message'], equals(created['message'])); + + Postcard canyon = new Postcard(location: "Grand Canyon", + message: "But did you REALLY experience it???"); + created = await clientTypedPostcards.create(canyon); + print(god.serialize(created)); + + expect(created.location, equals(canyon.location)); + expect(created.message, equals(canyon.message)); + + read = await clientTypedPostcards.read(created.id); + print(god.serialize(read)); + expect(read.id, equals(created.id)); + expect(read.location, equals(created.location)); + expect(read.message, equals(created.message)); + }); + + test("modify/update", () async { + server.MemoryService innerPostcards = serverPostcards.inner; + print(innerPostcards.items); + Postcard mecca = await clientTypedPostcards.create( + new Postcard(location: "Mecca", message: "Pilgrimage")); + print(god.serialize(mecca)); + + // I'm too lazy to write the tests twice, because I know it works + // So I'll modify using the type-based client, and update using the + // map-based one + + print("Postcards on server: " + + god.serialize(await serverPostcards.index())); + print("Postcards on client: " + + god.serialize(await clientPostcards.index())); + + Postcard modified = await clientTypedPostcards.modify( + mecca.id, {"location": "Saudi Arabia"}); + print(god.serialize(modified)); + expect(modified.id, equals(mecca.id)); + expect(modified.location, equals("Saudi Arabia")); + expect(modified.message, equals(mecca.message)); + + Map updated = await clientPostcards.update( + mecca.id, {"location": "Full", "message": "Overwrite"}); + print(updated); + + expect(updated.keys.length, equals(3)); + expect(updated['id'], equals(mecca.id)); + expect(updated['location'], equals("Full")); + expect(updated['message'], equals("Overwrite")); + }); + + test("remove", () async { + Postcard remove1 = await clientTypedPostcards.create( + {"location": "remove", "message": "#1"}); + Postcard remove2 = await clientTypedPostcards.create( + {"location": "remove", "message": "#2"}); + print(god.serialize([remove1, remove2])); + + Map removed1 = await clientPostcards.remove(remove1.id); + expect(removed1.keys.length, equals(3)); + expect(removed1['id'], equals(remove1.id)); + expect(removed1['location'], equals(remove1.location)); + expect(removed1['message'], equals(remove1.message)); + + Postcard removed2 = await clientTypedPostcards.remove(remove2.id); + expect(removed2, equals(remove2)); }); }); -} +} \ No newline at end of file diff --git a/test/shared.dart b/test/shared.dart index 4a8d2052..4b6621a0 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -1,6 +1,20 @@ -class Postcard { - String location; - String message; +import 'package:angel_framework/angel_framework.dart'; + +class Postcard extends MemoryModel { + int id; + String location; + String message; + + Postcard({String this.location, String this.message}); + + @override + bool operator ==(other) { + if (!(other is Postcard)) + return false; + + return id == other.id && location == other.location && + message == other.message; + } + - Postcard({String this.location, String this.message}); } \ No newline at end of file From 2d9132159eef9a30b639fdbc2447fd5ba332421a Mon Sep 17 00:00:00 2001 From: regiostech Date: Fri, 24 Jun 2016 17:07:31 -0400 Subject: [PATCH 05/69] Fixed dep --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e303f079..56c70487 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,6 +7,6 @@ dependencies: json_god: ">=2.0.0-beta <3.0.0" merge_map: ">=1.0.0 <2.0.0" dev_dependencies: - angel_framework: 1.0.0-dev+5 + angel_framework: ">=1.0.0-dev <2.0.0" http: ">= 0.11.3 < 0.12.0" test: ">= 0.12.13 < 0.13.0" \ No newline at end of file From 2a1f0ec73610ac5900dd8c8b028dd4026608aa72 Mon Sep 17 00:00:00 2001 From: regiostech Date: Fri, 24 Jun 2016 18:35:46 -0400 Subject: [PATCH 06/69] http should be a required dep --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 56c70487..61969cd9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,12 @@ name: angel_client -version: 1.0.0-dev +version: 1.0.0-dev+1 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client dependencies: + http: ">= 0.11.3 < 0.12.0" json_god: ">=2.0.0-beta <3.0.0" merge_map: ">=1.0.0 <2.0.0" dev_dependencies: angel_framework: ">=1.0.0-dev <2.0.0" - http: ">= 0.11.3 < 0.12.0" test: ">= 0.12.13 < 0.13.0" \ No newline at end of file From 6f7321a4af8d33f3547a349263cf6cd1526c9331 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 25 Jun 2016 14:37:49 -0400 Subject: [PATCH 07/69] Browser --- README.md | 10 +-- lib/browser.dart | 92 +++++++++++++++++++++++++ lib/cli.dart | 110 +++++++++++++++++++++++++++++ lib/shared.dart | 44 ++++++++++++ test/browser.dart | 10 +++ test/cli.dart | 134 ++++++++++++++++++++++++++++++++++++ test/for_browser_tests.dart | 20 ++++++ test/index.html | 10 +++ test/packages | 1 + 9 files changed, 427 insertions(+), 4 deletions(-) create mode 100644 lib/browser.dart create mode 100644 lib/cli.dart create mode 100644 lib/shared.dart create mode 100644 test/browser.dart create mode 100644 test/cli.dart create mode 100644 test/for_browser_tests.dart create mode 100644 test/index.html create mode 120000 test/packages diff --git a/README.md b/README.md index f90b691c..b96c82f1 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,17 @@ Client library for the Angel framework. # Isomorphic -The REST client depends on `http`, because it can run in the browser or on the command-line. -Depending on your environment, you must pass an instance of `BaseClient` to the constructor. +The REST client can run in the browser or on the command-line. # Usage This library provides the same API as an Angel server. ```dart -import 'package:angel_client/angel_client.dart'; -import 'package:http/browser_client.dart'; +// Import this file to import symbols "Angel" and "Service" +import 'package:angel_cli/shared.dart'; +// Choose one or the other, depending on platform +import 'package:angel_client/cli.dart'; +import 'package:angel_client/browser.dart'; main() async { Angel app = new Rest("http://localhost:3000", new BrowserClient()); diff --git a/lib/browser.dart b/lib/browser.dart new file mode 100644 index 00000000..86ffa0e7 --- /dev/null +++ b/lib/browser.dart @@ -0,0 +1,92 @@ +/// Browser library for the Angel framework. +library angel_client.browser; + +import 'dart:async'; +import 'dart:convert' show JSON; +import 'dart:html'; +import 'shared.dart'; + +_buildQuery(Map params) { + if (params == null || params == {}) + return ""; + + String result = ""; + return result; +} + +/// Queries an Angel server via REST. +class Rest extends Angel { + Rest(String basePath) :super(basePath); + + @override + RestService service(String path, {Type type}) { + String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), ""); + return new RestService._base("$basePath/$uri") + ..app = this; + } +} + +/// Queries an Angel service via REST. +class RestService extends Service { + String basePath; + + RestService._base(Pattern path) { + this.basePath = (path is RegExp) ? path.pattern : path; + } + + _makeBody(data) { + return JSON.encode(data); + } + + HttpRequest _buildRequest(String url, + {String method: "POST", bool write: true}) { + HttpRequest request = new HttpRequest(); + request.open(method, url, async: false); + request.responseType = "json"; + request.setRequestHeader("Accept", "application/json"); + if (write) + request.setRequestHeader("Content-Type", "application/json"); + return request; + } + + @override + Future index([Map params]) async { + return JSON.decode( + await HttpRequest.getString("$basePath/${_buildQuery(params)}")); + } + + @override + Future read(id, [Map params]) async { + return JSON.decode( + await HttpRequest.getString("$basePath/$id${_buildQuery(params)}")); + } + + @override + Future create(data, [Map params]) async { + var request = _buildRequest("$basePath/${_buildQuery(params)}"); + request.send(_makeBody(data)); + return request.response; + } + + @override + Future modify(id, data, [Map params]) async { + var request = _buildRequest("$basePath/$id${_buildQuery(params)}", method: "PATCH"); + request.send(_makeBody(data)); + return request.response; + } + + @override + Future update(id, data, [Map params]) async { + var request = _buildRequest("$basePath/$id${_buildQuery(params)}"); + request.send(_makeBody(data)); + return request.response; + } + + @override + Future remove(id, [Map params]) async { + var request = _buildRequest("$basePath/$id${_buildQuery(params)}", method: "DELETE"); + request.send(); + return request.response; + } +} + diff --git a/lib/cli.dart b/lib/cli.dart new file mode 100644 index 00000000..837c70ac --- /dev/null +++ b/lib/cli.dart @@ -0,0 +1,110 @@ +/// Command-line client library for the Angel framework. +library angel_client.cli; + +import 'dart:async'; +import 'dart:convert' show JSON; +import 'package:http/http.dart'; +import 'package:json_god/json_god.dart' as god; +import 'shared.dart'; + +_buildQuery(Map params) { + if (params == null || params == {}) + return ""; + + String result = ""; + return result; +} + +const Map _readHeaders = const { + "Accept": "application/json" +}; + +const Map _writeHeaders = const { + "Accept": "application/json", + "Content-Type": "application/json" +}; + +/// Queries an Angel server via REST. +class Rest extends Angel { + BaseClient client; + + Rest(String path, BaseClient this.client) :super(path); + + @override + RestService service(String path, {Type type}) { + String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), ""); + return new RestService._base("$basePath/$uri", client, type) + ..app = this; + } +} + +/// Queries an Angel service via REST. +class RestService extends Service { + String basePath; + BaseClient client; + Type outputType; + + RestService._base(Pattern path, BaseClient this.client, + Type this.outputType) { + this.basePath = (path is RegExp) ? path.pattern : path; + } + + _makeBody(data) { + if (outputType == null) + return JSON.encode(data); + else return god.serialize(data); + } + + @override + Future index([Map params]) async { + var response = await client.get( + "$basePath/${_buildQuery(params)}", headers: _readHeaders); + + if (outputType == null) + return god.deserialize(response.body); + + else { + return JSON.decode(response.body).map((x) => + god.deserializeDatum(x, outputType: outputType)).toList(); + } + } + + @override + Future read(id, [Map params]) async { + var response = await client.get( + "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); + return god.deserialize(response.body, outputType: outputType); + } + + @override + Future create(data, [Map params]) async { + var response = await client.post( + "$basePath/${_buildQuery(params)}", body: _makeBody(data), + headers: _writeHeaders); + return god.deserialize(response.body, outputType: outputType); + } + + @override + Future modify(id, data, [Map params]) async { + var response = await client.patch( + "$basePath/$id${_buildQuery(params)}", body: _makeBody(data), + headers: _writeHeaders); + return god.deserialize(response.body, outputType: outputType); + } + + @override + Future update(id, data, [Map params]) async { + var response = await client.patch( + "$basePath/$id${_buildQuery(params)}", body: _makeBody(data), + headers: _writeHeaders); + return god.deserialize(response.body, outputType: outputType); + } + + @override + Future remove(id, [Map params]) async { + var response = await client.delete( + "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); + return god.deserialize(response.body, outputType: outputType); + } +} + diff --git a/lib/shared.dart b/lib/shared.dart new file mode 100644 index 00000000..654b3167 --- /dev/null +++ b/lib/shared.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +/// A function that configures an [Angel] client in some way. +typedef Future AngelConfigurer(Angel app); + +/// Represents an Angel server that we are querying. +abstract class Angel { + /// The URL of the server. + String basePath; + + Angel(String this.basePath); + + /// Applies an [AngelConfigurer] to this instance. + Future configure(AngelConfigurer configurer) async { + await configurer(this); + } + + /// Returns a representation of a service on the server. + Service service(Pattern path, {Type type}); +} + +/// Queries a service on an Angel server, with the same API. +abstract class Service { + /// The Angel instance powering this service. + Angel app; + + /// Retrieves all resources. + Future index([Map params]); + + /// Retrieves the desired resource. + Future read(id, [Map params]); + + /// Creates a resource. + Future create(data, [Map params]); + + /// Modifies a resource. + Future modify(id, data, [Map params]); + + /// Overwrites a resource. + Future update(id, data, [Map params]); + + /// Removes the given resource. + Future remove(id, [Map params]); +} \ No newline at end of file diff --git a/test/browser.dart b/test/browser.dart new file mode 100644 index 00000000..a5b5295f --- /dev/null +++ b/test/browser.dart @@ -0,0 +1,10 @@ +import 'package:angel_client/shared.dart'; +import 'package:angel_client/browser.dart'; +import 'package:test/test.dart'; + +main() async { + Angel app = new Rest("http://localhost:3000"); + Service Todos = app.service("todos"); + + print(await Todos.index()); +} \ No newline at end of file diff --git a/test/cli.dart b/test/cli.dart new file mode 100644 index 00000000..f10192d7 --- /dev/null +++ b/test/cli.dart @@ -0,0 +1,134 @@ +import 'dart:io'; +import 'package:angel_client/shared.dart' as clientLib; +import 'package:angel_client/cli.dart' as client; +import 'package:angel_framework/angel_framework.dart' as server; +import 'package:http/http.dart' as http; +import 'package:json_god/json_god.dart' as god; +import 'package:test/test.dart'; +import 'shared.dart'; + +main() { + group("rest", () { + server.Angel serverApp = new server.Angel(); + server.HookedService serverPostcards; + clientLib.Angel clientApp; + clientLib.Service clientPostcards; + clientLib.Service clientTypedPostcards; + HttpServer httpServer; + + setUp(() async { + httpServer = + await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); + serverApp.use("/postcards", new server.MemoryService()); + serverPostcards = serverApp.service("postcards"); + + clientApp = new client.Rest("http://localhost:3000", new http.Client()); + clientPostcards = clientApp.service("postcards"); + clientTypedPostcards = clientApp.service("postcards", type: Postcard); + }); + + tearDown(() async { + await httpServer.close(force: true); + }); + + test("index", () async { + Postcard niagaraFalls = await serverPostcards.create( + new Postcard(location: "Niagara Falls", message: "Missing you!")); + List indexed = await clientPostcards.index(); + print(indexed); + + expect(indexed.length, equals(1)); + expect(indexed[0].keys.length, equals(3)); + expect(indexed[0]['id'], equals(niagaraFalls.id)); + expect(indexed[0]['location'], equals(niagaraFalls.location)); + expect(indexed[0]['message'], equals(niagaraFalls.message)); + + Postcard louvre = await serverPostcards.create(new Postcard( + location: "The Louvre", message: "The Mona Lisa was watching me!")); + print(god.serialize(louvre)); + List typedIndexed = await clientTypedPostcards.index(); + expect(typedIndexed.length, equals(2)); + expect(typedIndexed[1], equals(louvre)); + }); + + test("create/read", () async { + Map opry = {"location": "Grand Ole Opry", "message": "Yeehaw!"}; + var created = await clientPostcards.create(opry); + print(created); + + expect(created['id'] == null, equals(false)); + expect(created["location"], equals(opry["location"])); + expect(created["message"], equals(opry["message"])); + + var read = await clientPostcards.read(created['id']); + print(read); + expect(read['id'], equals(created['id'])); + expect(read['location'], equals(created['location'])); + expect(read['message'], equals(created['message'])); + + Postcard canyon = new Postcard(location: "Grand Canyon", + message: "But did you REALLY experience it???"); + created = await clientTypedPostcards.create(canyon); + print(god.serialize(created)); + + expect(created.location, equals(canyon.location)); + expect(created.message, equals(canyon.message)); + + read = await clientTypedPostcards.read(created.id); + print(god.serialize(read)); + expect(read.id, equals(created.id)); + expect(read.location, equals(created.location)); + expect(read.message, equals(created.message)); + }); + + test("modify/update", () async { + server.MemoryService innerPostcards = serverPostcards.inner; + print(innerPostcards.items); + Postcard mecca = await clientTypedPostcards.create( + new Postcard(location: "Mecca", message: "Pilgrimage")); + print(god.serialize(mecca)); + + // I'm too lazy to write the tests twice, because I know it works + // So I'll modify using the type-based client, and update using the + // map-based one + + print("Postcards on server: " + + god.serialize(await serverPostcards.index())); + print("Postcards on client: " + + god.serialize(await clientPostcards.index())); + + Postcard modified = await clientTypedPostcards.modify( + mecca.id, {"location": "Saudi Arabia"}); + print(god.serialize(modified)); + expect(modified.id, equals(mecca.id)); + expect(modified.location, equals("Saudi Arabia")); + expect(modified.message, equals(mecca.message)); + + Map updated = await clientPostcards.update( + mecca.id, {"location": "Full", "message": "Overwrite"}); + print(updated); + + expect(updated.keys.length, equals(3)); + expect(updated['id'], equals(mecca.id)); + expect(updated['location'], equals("Full")); + expect(updated['message'], equals("Overwrite")); + }); + + test("remove", () async { + Postcard remove1 = await clientTypedPostcards.create( + {"location": "remove", "message": "#1"}); + Postcard remove2 = await clientTypedPostcards.create( + {"location": "remove", "message": "#2"}); + print(god.serialize([remove1, remove2])); + + Map removed1 = await clientPostcards.remove(remove1.id); + expect(removed1.keys.length, equals(3)); + expect(removed1['id'], equals(remove1.id)); + expect(removed1['location'], equals(remove1.location)); + expect(removed1['message'], equals(remove1.message)); + + Postcard removed2 = await clientTypedPostcards.remove(remove2.id); + expect(removed2, equals(remove2)); + }); + }); +} \ No newline at end of file diff --git a/test/for_browser_tests.dart b/test/for_browser_tests.dart new file mode 100644 index 00000000..ea5253cc --- /dev/null +++ b/test/for_browser_tests.dart @@ -0,0 +1,20 @@ +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; + +main() async { + Angel app = new Angel(); + app.before.add((req, ResponseContext res) { + res.header("Access-Control-Allow-Origin", "*"); + }); + + app.use("/todos", new MemoryService()); + + await app.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); + print("Server up on localhost:3000"); +} + +class Todo extends MemoryModel { + String hello; + + Todo({String this.hello}); +} \ No newline at end of file diff --git a/test/index.html b/test/index.html new file mode 100644 index 00000000..6d164dc3 --- /dev/null +++ b/test/index.html @@ -0,0 +1,10 @@ + + + + + Browser + + + + + \ No newline at end of file diff --git a/test/packages b/test/packages new file mode 120000 index 00000000..a16c4050 --- /dev/null +++ b/test/packages @@ -0,0 +1 @@ +../packages \ No newline at end of file From 09613bbfa31a15dc9715127ee1267ba491e951b0 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Sep 2016 08:02:32 -0400 Subject: [PATCH 08/69] Restructured --- .DS_Store | Bin 0 -> 6148 bytes lib/angel_client.dart | 7 +- lib/browser.dart | 18 ++--- lib/cli.dart | 4 +- lib/rest.dart | 101 --------------------------- lib/shared.dart | 44 ------------ pubspec.yaml | 4 +- test/.DS_Store | Bin 0 -> 6148 bytes test/browser.dart | 11 +-- test/cli.dart | 16 ++--- test/for_browser_tests.dart | 9 +-- test/rest.dart | 133 ------------------------------------ test/shared.dart | 2 +- 13 files changed, 34 insertions(+), 315 deletions(-) create mode 100644 .DS_Store delete mode 100644 lib/rest.dart delete mode 100644 lib/shared.dart create mode 100644 test/.DS_Store delete mode 100644 test/rest.dart diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a2eb533abf551d042629249e6fe5fb378b3b5ee0 GIT binary patch literal 6148 zcmeH~u?oUK42Bc!Ah>jNyu}Cb4Gz&K=nFU~E>c0O^F6wMazU^xC+1b{XuyJ79K1T}(S!u1*@b}wNMJ-@TJzTK|1JE}{6A`8N&+PC zX9Tp_belC^D(=>|*R%RAs index([Map params]) async { - var response = await client.get( - "$basePath/${_buildQuery(params)}", headers: _readHeaders); - - if (outputType == null) - return god.deserialize(response.body); - - else { - return JSON.decode(response.body).map((x) => - god.deserializeDatum(x, outputType: outputType)).toList(); - } - } - - @override - Future read(id, [Map params]) async { - var response = await client.get( - "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); - return god.deserialize(response.body, outputType: outputType); - } - - @override - Future create(data, [Map params]) async { - var response = await client.post( - "$basePath/${_buildQuery(params)}", body: _makeBody(data), - headers: _writeHeaders); - return god.deserialize(response.body, outputType: outputType); - } - - @override - Future modify(id, data, [Map params]) async { - var response = await client.patch( - "$basePath/$id${_buildQuery(params)}", body: _makeBody(data), - headers: _writeHeaders); - return god.deserialize(response.body, outputType: outputType); - } - - @override - Future update(id, data, [Map params]) async { - var response = await client.patch( - "$basePath/$id${_buildQuery(params)}", body: _makeBody(data), - headers: _writeHeaders); - return god.deserialize(response.body, outputType: outputType); - } - - @override - Future remove(id, [Map params]) async { - var response = await client.delete( - "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); - return god.deserialize(response.body, outputType: outputType); - } -} diff --git a/lib/shared.dart b/lib/shared.dart deleted file mode 100644 index 654b3167..00000000 --- a/lib/shared.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:async'; - -/// A function that configures an [Angel] client in some way. -typedef Future AngelConfigurer(Angel app); - -/// Represents an Angel server that we are querying. -abstract class Angel { - /// The URL of the server. - String basePath; - - Angel(String this.basePath); - - /// Applies an [AngelConfigurer] to this instance. - Future configure(AngelConfigurer configurer) async { - await configurer(this); - } - - /// Returns a representation of a service on the server. - Service service(Pattern path, {Type type}); -} - -/// Queries a service on an Angel server, with the same API. -abstract class Service { - /// The Angel instance powering this service. - Angel app; - - /// Retrieves all resources. - Future index([Map params]); - - /// Retrieves the desired resource. - Future read(id, [Map params]); - - /// Creates a resource. - Future create(data, [Map params]); - - /// Modifies a resource. - Future modify(id, data, [Map params]); - - /// Overwrites a resource. - Future update(id, data, [Map params]); - - /// Removes the given resource. - Future remove(id, [Map params]); -} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 61969cd9..a51c40b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+1 +version: 1.0.0-dev+6 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client @@ -9,4 +9,4 @@ dependencies: merge_map: ">=1.0.0 <2.0.0" dev_dependencies: angel_framework: ">=1.0.0-dev <2.0.0" - test: ">= 0.12.13 < 0.13.0" \ No newline at end of file + test: ">= 0.12.13 < 0.13.0" diff --git a/test/.DS_Store b/test/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f17ada42b9498f0bc5247e6446dd56a64a31694e GIT binary patch literal 6148 zcmeHKOHRW;41F#YDwIW6h-H>tA#sDKDoAYM0EK>_h(J+N1j`(QLva9ZQ=Vt6s!f_L zs*o+&Z!)pRGjB$527nCSrx!pEK!>W>I-=Pj`d+mrE$cZXN*JTS3^~p*#eL1&JNA(Q z-MbTvxoZo2U~~U+EU&Z0WSlL=^w9(IP3^COu%(A$f(%c#-!*@^sraAbfmw2lD)~lO z^4t`(MdMgMm9dVQ_m$^thEK-K8T*2)GT$qWp~`AStMh&1xt!pc_E9tL@c-IaUzhWl z(>JH@v}tC6mCdM-%8fJN3^)UO%>Zk*N~asqtux>ZI0I`2^!t!d6;s19V)%5>gew5C z&F&y<>#w55B!;PB8If-&5<`hGRP2f&F`WI0`K5+s#25~-%ZFHIu^Wnn>>NJ{?vOO1 zTW7!-*k<4$KK8W!UpC+Ww}br28E^*v6$8>9-VO&`lGWDQ;()); serverPostcards = serverApp.service("postcards"); - clientApp = new client.Rest("http://localhost:3000", new http.Client()); + clientApp = new client.Rest(url, new http.Client()); clientPostcards = clientApp.service("postcards"); clientTypedPostcards = clientApp.service("postcards", type: Postcard); }); @@ -131,4 +131,4 @@ main() { expect(removed2, equals(remove2)); }); }); -} \ No newline at end of file +} diff --git a/test/for_browser_tests.dart b/test/for_browser_tests.dart index ea5253cc..072f1d52 100644 --- a/test/for_browser_tests.dart +++ b/test/for_browser_tests.dart @@ -1,5 +1,6 @@ import 'dart:io'; -import 'package:angel_framework/angel_framework.dart'; +import "package:angel_framework/angel_framework.dart"; +import "package:angel_framework/defs.dart"; main() async { Angel app = new Angel(); @@ -9,12 +10,12 @@ main() async { app.use("/todos", new MemoryService()); - await app.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); - print("Server up on localhost:3000"); + await app.startServer(InternetAddress.LOOPBACK_IP_V4, 3001); + print("Server up on localhost:3001"); } class Todo extends MemoryModel { String hello; Todo({String this.hello}); -} \ No newline at end of file +} diff --git a/test/rest.dart b/test/rest.dart deleted file mode 100644 index 0bcdb6b8..00000000 --- a/test/rest.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'dart:io'; -import 'package:angel_client/angel_client.dart' as client; -import 'package:angel_framework/angel_framework.dart' as server; -import 'package:http/http.dart' as http; -import 'package:json_god/json_god.dart' as god; -import 'package:test/test.dart'; -import 'shared.dart'; - -main() { - group("rest", () { - server.Angel serverApp = new server.Angel(); - server.HookedService serverPostcards; - client.Angel clientApp; - client.Service clientPostcards; - client.Service clientTypedPostcards; - HttpServer httpServer; - - setUp(() async { - httpServer = - await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 3000); - serverApp.use("/postcards", new server.MemoryService()); - serverPostcards = serverApp.service("postcards"); - - clientApp = new client.Rest("http://localhost:3000", new http.Client()); - clientPostcards = clientApp.service("postcards"); - clientTypedPostcards = clientApp.service("postcards", type: Postcard); - }); - - tearDown(() async { - await httpServer.close(force: true); - }); - - test("index", () async { - Postcard niagaraFalls = await serverPostcards.create( - new Postcard(location: "Niagara Falls", message: "Missing you!")); - List indexed = await clientPostcards.index(); - print(indexed); - - expect(indexed.length, equals(1)); - expect(indexed[0].keys.length, equals(3)); - expect(indexed[0]['id'], equals(niagaraFalls.id)); - expect(indexed[0]['location'], equals(niagaraFalls.location)); - expect(indexed[0]['message'], equals(niagaraFalls.message)); - - Postcard louvre = await serverPostcards.create(new Postcard( - location: "The Louvre", message: "The Mona Lisa was watching me!")); - print(god.serialize(louvre)); - List typedIndexed = await clientTypedPostcards.index(); - expect(typedIndexed.length, equals(2)); - expect(typedIndexed[1], equals(louvre)); - }); - - test("create/read", () async { - Map opry = {"location": "Grand Ole Opry", "message": "Yeehaw!"}; - var created = await clientPostcards.create(opry); - print(created); - - expect(created['id'] == null, equals(false)); - expect(created["location"], equals(opry["location"])); - expect(created["message"], equals(opry["message"])); - - var read = await clientPostcards.read(created['id']); - print(read); - expect(read['id'], equals(created['id'])); - expect(read['location'], equals(created['location'])); - expect(read['message'], equals(created['message'])); - - Postcard canyon = new Postcard(location: "Grand Canyon", - message: "But did you REALLY experience it???"); - created = await clientTypedPostcards.create(canyon); - print(god.serialize(created)); - - expect(created.location, equals(canyon.location)); - expect(created.message, equals(canyon.message)); - - read = await clientTypedPostcards.read(created.id); - print(god.serialize(read)); - expect(read.id, equals(created.id)); - expect(read.location, equals(created.location)); - expect(read.message, equals(created.message)); - }); - - test("modify/update", () async { - server.MemoryService innerPostcards = serverPostcards.inner; - print(innerPostcards.items); - Postcard mecca = await clientTypedPostcards.create( - new Postcard(location: "Mecca", message: "Pilgrimage")); - print(god.serialize(mecca)); - - // I'm too lazy to write the tests twice, because I know it works - // So I'll modify using the type-based client, and update using the - // map-based one - - print("Postcards on server: " + - god.serialize(await serverPostcards.index())); - print("Postcards on client: " + - god.serialize(await clientPostcards.index())); - - Postcard modified = await clientTypedPostcards.modify( - mecca.id, {"location": "Saudi Arabia"}); - print(god.serialize(modified)); - expect(modified.id, equals(mecca.id)); - expect(modified.location, equals("Saudi Arabia")); - expect(modified.message, equals(mecca.message)); - - Map updated = await clientPostcards.update( - mecca.id, {"location": "Full", "message": "Overwrite"}); - print(updated); - - expect(updated.keys.length, equals(3)); - expect(updated['id'], equals(mecca.id)); - expect(updated['location'], equals("Full")); - expect(updated['message'], equals("Overwrite")); - }); - - test("remove", () async { - Postcard remove1 = await clientTypedPostcards.create( - {"location": "remove", "message": "#1"}); - Postcard remove2 = await clientTypedPostcards.create( - {"location": "remove", "message": "#2"}); - print(god.serialize([remove1, remove2])); - - Map removed1 = await clientPostcards.remove(remove1.id); - expect(removed1.keys.length, equals(3)); - expect(removed1['id'], equals(remove1.id)); - expect(removed1['location'], equals(remove1.location)); - expect(removed1['message'], equals(remove1.message)); - - Postcard removed2 = await clientTypedPostcards.remove(remove2.id); - expect(removed2, equals(remove2)); - }); - }); -} \ No newline at end of file diff --git a/test/shared.dart b/test/shared.dart index 4b6621a0..a7f4e662 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -1,4 +1,4 @@ -import 'package:angel_framework/angel_framework.dart'; +import "package:angel_framework/defs.dart"; class Postcard extends MemoryModel { int id; From 601e958dd7d5c3fe530bbb87db1b0a50246a0f4b Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Sep 2016 08:04:00 -0400 Subject: [PATCH 09/69] Readme --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b96c82f1..b439a615 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ The REST client can run in the browser or on the command-line. This library provides the same API as an Angel server. ```dart -// Import this file to import symbols "Angel" and "Service" -import 'package:angel_cli/shared.dart'; // Choose one or the other, depending on platform import 'package:angel_client/cli.dart'; import 'package:angel_client/browser.dart'; @@ -31,7 +29,7 @@ foo() async { } ``` -The REST client also supports reflection via `json_god`. There is no need to work with Maps; +The CLI client also supports reflection via `json_god`. There is no need to work with Maps; you can use the same class on the client and the server. ```dart @@ -50,4 +48,4 @@ bar() async { ``` Just like on the server, services support `index`, `read`, `create`, `modify`, `update` and -`remove`. \ No newline at end of file +`remove`. From 29ecf461dd986ebadefab721645fe6c2752b4d89 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Sep 2016 08:04:16 -0400 Subject: [PATCH 10/69] Readme --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a51c40b7..b516623c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+6 +version: 1.0.0-dev+7 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 319e195faa047f6ec75568b9eace2f871b38c7bf Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 27 Nov 2016 22:28:41 -0500 Subject: [PATCH 11/69] Finish after YoDesk --- lib/angel_client.dart | 11 ++- lib/auth_types.dart | 2 + lib/browser.dart | 143 ++++++++++++++++++++++++-------- lib/{cli.dart => io.dart} | 0 pubspec.yaml | 2 +- test/for_browser_tests.dart | 2 +- test/{cli.dart => io_test.dart} | 2 +- test/packages | 1 - 8 files changed, 125 insertions(+), 38 deletions(-) create mode 100644 lib/auth_types.dart rename lib/{cli.dart => io.dart} (100%) rename test/{cli.dart => io_test.dart} (99%) delete mode 120000 test/packages diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 9ecc1526..9fdce34b 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -2,6 +2,7 @@ library angel_client; import 'dart:async'; +import 'auth_types.dart' as auth_types; /// A function that configures an [Angel] client in some way. typedef Future AngelConfigurer(Angel app); @@ -12,6 +13,8 @@ abstract class Angel { Angel(String this.basePath); + Future authenticate({String type: auth_types.LOCAL, credentials, String authEndpoint: '/auth'}); + /// Applies an [AngelConfigurer] to this instance. Future configure(AngelConfigurer configurer) async { await configurer(this); @@ -20,10 +23,16 @@ abstract class Angel { Service service(Pattern path, {Type type}); } +/// Represents the result of authentication with an Angel server. +abstract class AngelAuthResult { + Map get data; + String get token; +} + /// Queries a service on an Angel server, with the same API. abstract class Service { /// The Angel instance powering this service. - Angel app; + Angel get app; /// Retrieves all resources. Future index([Map params]); diff --git a/lib/auth_types.dart b/lib/auth_types.dart new file mode 100644 index 00000000..4328fa16 --- /dev/null +++ b/lib/auth_types.dart @@ -0,0 +1,2 @@ +const String GOOGLE = 'google'; +const String LOCAL = 'local'; \ No newline at end of file diff --git a/lib/browser.dart b/lib/browser.dart index 79307dd9..85b649df 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -1,92 +1,169 @@ /// Browser library for the Angel framework. library angel_client.browser; -import 'dart:async'; +import 'dart:async' show Completer, Future; import 'dart:convert' show JSON; -import 'dart:html'; +import 'dart:html' show HttpRequest; import 'angel_client.dart'; +import 'auth_types.dart' as auth_types; export 'angel_client.dart'; _buildQuery(Map params) { - if (params == null || params == {}) - return ""; + if (params == null || params == {}) return ""; String result = ""; return result; } +_send(HttpRequest request, [data]) { + final completer = new Completer(); + + 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); + } + }); + + request.send(data); + return completer.future; +} + /// Queries an Angel server via REST. class Rest extends Angel { - Rest(String basePath) :super(basePath); + String _authToken; + + Rest(String basePath) : super(basePath); + + @override + Future authenticate( + {String type: auth_types.LOCAL, + credentials, + String authEndpoint: '/auth'}) async { + final url = '$authEndpoint/$type'; + + 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((_) { + completer + .complete(new _AngelAuthResultImpl.fromMap(request.response)); + }) + ..onError.listen((_) { + try { + throw new Exception( + 'Request failed with status code ${request.status}.'); + } catch (e, st) { + completer.completeError(e, st); + } + }); + + request.send(credentials); + + return completer.future; + } else { + throw new Exception('angel_client cannot authenticate as "$type" yet.'); + } + } @override RestService service(String path, {Type type}) { String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), ""); - return new RestService("$basePath/$uri") - ..app = this; + return new _RestServiceImpl(this, "$basePath/$uri"); } } -/// Queries an Angel service via REST. -class RestService extends Service { - String basePath; +abstract class RestService extends Service { + RestService._(String basePath); +} - RestService(Pattern path) { - this.basePath = (path is RegExp) ? path.pattern : path; +class _AngelAuthResultImpl implements AngelAuthResult { + final Map data = {}; + final String token; + + _AngelAuthResultImpl({this.token, Map data: const {}}) { + this.data.addAll(data ?? {}); + } + + factory _AngelAuthResultImpl.fromMap(Map data) => + new _AngelAuthResultImpl(token: data['token'], data: data['data']); +} + +/// Queries an Angel service via REST. +class _RestServiceImpl extends RestService { + final Rest app; + String _basePath; + String get basePath => _basePath; + + _RestServiceImpl(this.app, String basePath) : super._(basePath) { + _basePath = basePath; } _makeBody(data) { return JSON.encode(data); } - HttpRequest buildRequest(String url, - {String method: "POST", bool write: true}) { + Future buildRequest(String url, + {String method: "POST", bool write: true}) async { HttpRequest request = new HttpRequest(); - request.open(method, url, async: false); + request.open(method, url); request.responseType = "json"; request.setRequestHeader("Accept", "application/json"); - if (write) - request.setRequestHeader("Content-Type", "application/json"); + if (write) request.setRequestHeader("Content-Type", "application/json"); + if (app._authToken != null) + request.setRequestHeader("Authorization", "Bearer ${app._authToken}"); return request; } @override Future index([Map params]) async { - return JSON.decode( - await HttpRequest.getString("$basePath/${_buildQuery(params)}")); + final request = await buildRequest('$basePath/${_buildQuery(params)}', + method: 'GET', write: false); + return await _send(request); } @override Future read(id, [Map params]) async { - return JSON.decode( - await HttpRequest.getString("$basePath/$id${_buildQuery(params)}")); + final request = await buildRequest('$basePath/$id${_buildQuery(params)}', + method: 'GET', write: false); + return await _send(request); } @override Future create(data, [Map params]) async { - var request = buildRequest("$basePath/${_buildQuery(params)}"); - request.send(_makeBody(data)); - return request.response; + final request = await buildRequest("$basePath/${_buildQuery(params)}"); + return await _send(request, _makeBody(data)); } @override Future modify(id, data, [Map params]) async { - var request = buildRequest("$basePath/$id${_buildQuery(params)}", method: "PATCH"); - request.send(_makeBody(data)); - return request.response; + final request = await buildRequest("$basePath/$id${_buildQuery(params)}", + method: "PATCH"); + return await _send(request, _makeBody(data)); } @override Future update(id, data, [Map params]) async { - var request = buildRequest("$basePath/$id${_buildQuery(params)}"); - request.send(_makeBody(data)); - return request.response; + final request = await buildRequest("$basePath/$id${_buildQuery(params)}"); + return await _send(request, _makeBody(data)); } @override Future remove(id, [Map params]) async { - var request = buildRequest("$basePath/$id${_buildQuery(params)}", method: "DELETE"); - request.send(); - return request.response; + final request = await buildRequest("$basePath/$id${_buildQuery(params)}", + method: "DELETE"); + return await _send(request); } } diff --git a/lib/cli.dart b/lib/io.dart similarity index 100% rename from lib/cli.dart rename to lib/io.dart diff --git a/pubspec.yaml b/pubspec.yaml index b516623c..0dbdcc30 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+7 +version: 1.0.0-dev+8 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/for_browser_tests.dart b/test/for_browser_tests.dart index 072f1d52..d020dc92 100644 --- a/test/for_browser_tests.dart +++ b/test/for_browser_tests.dart @@ -1,6 +1,6 @@ import 'dart:io'; import "package:angel_framework/angel_framework.dart"; -import "package:angel_framework/defs.dart"; +import "package:angel_framework/src/defs.dart"; main() async { Angel app = new Angel(); diff --git a/test/cli.dart b/test/io_test.dart similarity index 99% rename from test/cli.dart rename to test/io_test.dart index f632ca6d..894a3399 100644 --- a/test/cli.dart +++ b/test/io_test.dart @@ -1,5 +1,5 @@ import 'dart:io'; -import 'package:angel_client/cli.dart' as client; +import 'package:angel_client/io.dart' as client; import 'package:angel_framework/angel_framework.dart' as server; import 'package:http/http.dart' as http; import 'package:json_god/json_god.dart' as god; diff --git a/test/packages b/test/packages deleted file mode 120000 index a16c4050..00000000 --- a/test/packages +++ /dev/null @@ -1 +0,0 @@ -../packages \ No newline at end of file From 72f9054ecc900e8803c3bfdd71c6f93d37813168 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 27 Nov 2016 22:34:04 -0500 Subject: [PATCH 12/69] LocalStorage --- lib/browser.dart | 22 +++++++++++++++++++--- pubspec.yaml | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/browser.dart b/lib/browser.dart index 85b649df..1238c6f4 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -3,7 +3,7 @@ library angel_client.browser; import 'dart:async' show Completer, Future; import 'dart:convert' show JSON; -import 'dart:html' show HttpRequest; +import 'dart:html' show HttpRequest, window; import 'angel_client.dart'; import 'auth_types.dart' as auth_types; export 'angel_client.dart'; @@ -46,6 +46,19 @@ class Rest extends Angel { {String type: auth_types.LOCAL, credentials, String authEndpoint: '/auth'}) async { + if (type == null) { + if (window.localStorage.containsKey('user') && + window.localStorage.containsKey('token')) { + final result = new _AngelAuthResultImpl( + token: JSON.decode(window.localStorage['token']), + data: JSON.decode(window.localStorage['user'])); + _authToken = result.token; + return result; + } else { + throw new Exception('Failed to authenticate via localStorage.'); + } + } + final url = '$authEndpoint/$type'; if (type == auth_types.LOCAL) { @@ -58,8 +71,11 @@ class Rest extends Angel { request ..onLoadEnd.listen((_) { - completer - .complete(new _AngelAuthResultImpl.fromMap(request.response)); + 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 { diff --git a/pubspec.yaml b/pubspec.yaml index 0dbdcc30..1a0c8983 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+8 +version: 1.0.0-dev+9 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 5434b14abe6e948d0477f1bcf2dbf46d3ea43ade Mon Sep 17 00:00:00 2001 From: thosakwe Date: Mon, 28 Nov 2016 00:02:12 -0500 Subject: [PATCH 13/69] +10 --- lib/browser.dart | 5 ++++- pubspec.yaml | 2 +- test/shared.dart | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/browser.dart b/lib/browser.dart index 1238c6f4..058908ab 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -31,7 +31,10 @@ _send(HttpRequest request, [data]) { } }); - request.send(data); + if (data == null) + request.send(); + else + request.send(JSON.encode(data)); return completer.future; } diff --git a/pubspec.yaml b/pubspec.yaml index 1a0c8983..ae34dba8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+9 +version: 1.0.0-dev+10 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/shared.dart b/test/shared.dart index a7f4e662..d9d9a551 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -1,4 +1,4 @@ -import "package:angel_framework/defs.dart"; +import "package:angel_framework/src/defs.dart"; class Postcard extends MemoryModel { int id; From ac83e14f7f84c9bc7e169d829f632f2faa83a038 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Mon, 28 Nov 2016 00:05:20 -0500 Subject: [PATCH 14/69] +11 --- lib/browser.dart | 5 ++++- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/browser.dart b/lib/browser.dart index 058908ab..02752dfb 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -89,7 +89,10 @@ class Rest extends Angel { } }); - request.send(credentials); + if (credentials == null) + request.send(); + else + request.send(JSON.encode(credentials)); return completer.future; } else { diff --git a/pubspec.yaml b/pubspec.yaml index ae34dba8..e7f154a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+10 +version: 1.0.0-dev+11 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 8b892084456261464f1e896f6bf7af010b5907d8 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Mon, 28 Nov 2016 19:42:02 -0500 Subject: [PATCH 15/69] +12 --- lib/angel_client.dart | 6 +++++- lib/browser.dart | 42 ++++++++++++++++++++++++++++++------------ pubspec.yaml | 2 +- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 9fdce34b..f77e3a62 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -13,7 +13,11 @@ abstract class Angel { Angel(String this.basePath); - Future authenticate({String type: auth_types.LOCAL, credentials, String authEndpoint: '/auth'}); + Future authenticate( + {String type: auth_types.LOCAL, + credentials, + String authEndpoint: '/auth', + String reviveEndpoint: '/auth/token'}); /// Applies an [AngelConfigurer] to this instance. Future configure(AngelConfigurer configurer) async { diff --git a/lib/browser.dart b/lib/browser.dart index 02752dfb..c85852b4 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -48,21 +48,39 @@ class Rest extends Angel { Future authenticate( {String type: auth_types.LOCAL, credentials, - String authEndpoint: '/auth'}) async { + String authEndpoint: '/auth', + String reviveEndpoint: '/auth/token'}) async { if (type == null) { - if (window.localStorage.containsKey('user') && - window.localStorage.containsKey('token')) { - final result = new _AngelAuthResultImpl( - token: JSON.decode(window.localStorage['token']), - data: JSON.decode(window.localStorage['user'])); - _authToken = result.token; - return result; - } else { - throw new Exception('Failed to authenticate via localStorage.'); - } + final result = new _AngelAuthResultImpl( + token: JSON.decode(window.localStorage['token']), + data: JSON.decode(window.localStorage['user'])); + final completer = new Completer(); + final request = new HttpRequest(); + request.open('POST', '$basePath$reviveEndpoint'); + 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); + } + }); + + request.send(); + return completer.future; } - final url = '$authEndpoint/$type'; + final url = '$basePath$authEndpoint/$type'; if (type == auth_types.LOCAL) { final completer = new Completer(); diff --git a/pubspec.yaml b/pubspec.yaml index e7f154a3..14bf3b0a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+11 +version: 1.0.0-dev+12 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From c220a4883008cdae58aeef60873acbf6fc7cc526 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Dec 2016 13:21:44 -0500 Subject: [PATCH 16/69] 15 --- lib/angel_client.dart | 4 +++- lib/browser.dart | 41 +++++++++++++++++++++++++++++++++-------- pubspec.yaml | 2 +- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/lib/angel_client.dart b/lib/angel_client.dart index f77e3a62..81b10f21 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -14,7 +14,7 @@ abstract class Angel { Angel(String this.basePath); Future authenticate( - {String type: auth_types.LOCAL, + {String type, credentials, String authEndpoint: '/auth', String reviveEndpoint: '/auth/token'}); @@ -31,6 +31,8 @@ abstract class Angel { abstract class AngelAuthResult { Map get data; String get token; + + Map toJson(); } /// Queries a service on an Angel server, with the same API. diff --git a/lib/browser.dart b/lib/browser.dart index c85852b4..605ed677 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -33,6 +33,8 @@ _send(HttpRequest request, [data]) { if (data == null) request.send(); + else if (data is String) + request.send(data); else request.send(JSON.encode(data)); return completer.future; @@ -45,18 +47,25 @@ class Rest extends Angel { Rest(String basePath) : super(basePath); @override - Future authenticate( - {String type: auth_types.LOCAL, + Future authenticate( + {String type, credentials, String authEndpoint: '/auth', String reviveEndpoint: '/auth/token'}) async { if (type == null) { + if (!window.localStorage.containsKey('token')) { + throw new Exception( + 'Cannot revive token from localStorage - there is none.'); + } + final result = new _AngelAuthResultImpl( token: JSON.decode(window.localStorage['token']), data: JSON.decode(window.localStorage['user'])); final completer = new Completer(); - final request = new HttpRequest(); + final request = new HttpRequest()..responseType = 'json'; request.open('POST', '$basePath$reviveEndpoint'); + request.setRequestHeader('Accept', 'application/json'); + request.setRequestHeader('Content-Type', 'application/json'); request.setRequestHeader('Authorization', 'Bearer ${result.token}'); request @@ -76,7 +85,7 @@ class Rest extends Angel { } }); - request.send(); + request.send(JSON.encode(result)); return completer.future; } @@ -130,15 +139,31 @@ abstract class RestService extends Service { } class _AngelAuthResultImpl implements AngelAuthResult { + String _token; final Map data = {}; - final String token; + String get token => _token; + + _AngelAuthResultImpl({token, Map data: const {}}) { + if (token is String) _token = token; - _AngelAuthResultImpl({this.token, Map data: const {}}) { this.data.addAll(data ?? {}); } - factory _AngelAuthResultImpl.fromMap(Map data) => - new _AngelAuthResultImpl(token: data['token'], data: data['data']); + 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 toJson() { + return {'token': token, 'data': data}; + } } /// Queries an Angel service via REST. diff --git a/pubspec.yaml b/pubspec.yaml index 14bf3b0a..d9b1ce82 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+12 +version: 1.0.0-dev+15 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 90af9f9bb6726eb8ea2e613d27e003036064cba9 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 8 Dec 2016 19:24:07 -0500 Subject: [PATCH 17/69] Common code :) --- .travis.yml | 1 + README.md | 4 + lib/angel_client.dart | 34 ++++- lib/base_angel_client.dart | 278 +++++++++++++++++++++++++++++++++++++ lib/browser.dart | 234 ++++--------------------------- lib/io.dart | 111 +++++---------- pubspec.yaml | 4 +- test/io_test.dart | 2 +- 8 files changed, 374 insertions(+), 294 deletions(-) create mode 100644 .travis.yml create mode 100644 lib/base_angel_client.dart diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..de2210c9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: dart \ No newline at end of file diff --git a/README.md b/README.md index b439a615..84e85f9e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ # angel_client + +[![pub 1.0.0-dev+16](https://img.shields.io/badge/pub-1.0.0--dev+16-red.svg)](https://pub.dartlang.org/packages/angel_framework) +![build status](https://travis-ci.org/angel-dart/client.svg) + Client library for the Angel framework. # Isomorphic diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 81b10f21..b603c430 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -2,13 +2,15 @@ library angel_client; import 'dart:async'; -import 'auth_types.dart' as auth_types; +import 'dart:convert'; +export 'package:angel_framework/src/http/angel_http_exception.dart'; /// A function that configures an [Angel] client in some way. typedef Future AngelConfigurer(Angel app); /// Represents an Angel server that we are querying. abstract class Angel { + String get authToken; String basePath; Angel(String this.basePath); @@ -28,11 +30,33 @@ abstract class Angel { } /// Represents the result of authentication with an Angel server. -abstract class AngelAuthResult { - Map get data; - String get token; +class AngelAuthResult { + String _token; + final Map data = {}; + String get token => _token; - Map toJson(); + AngelAuthResult({String token, Map 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) + result._token = data['token']; + + if (data is Map) result.data.addAll(data['data'] ?? {}); + + return result; + } + + factory AngelAuthResult.fromJson(String json) => + new AngelAuthResult.fromMap(JSON.decode(json)); + + Map toJson() { + return {'token': token, 'data': data}; + } } /// Queries a service on an Angel server, with the same API. diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart new file mode 100644 index 00000000..ff6f17e7 --- /dev/null +++ b/lib/base_angel_client.dart @@ -0,0 +1,278 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:angel_framework/src/http/angel_http_exception.dart'; +import 'package:collection/collection.dart'; +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; +import 'package:merge_map/merge_map.dart'; +import 'angel_client.dart'; +import 'auth_types.dart' as auth_types; + +const Map _readHeaders = const {'Accept': 'application/json'}; +final Map _writeHeaders = mergeMap([ + _readHeaders, + const {'Content-Type': 'application/json'} +]); + +_buildQuery(Map params) { + if (params == null || params.isEmpty) return ""; + + List query = []; + + params.forEach((k, v) { + query.add('$k=$v'); + }); + + return '?' + query.join('&'); +} + +AngelHttpException failure(http.Response response, {error, StackTrace stack}) { + try { + final json = JSON.decode(response.body); + + if (json is Map && json['isError'] == true) { + return new AngelHttpException.fromMap(json); + } else { + return new AngelHttpException(error, + message: 'Unhandled exception while connecting to Angel backend.', + statusCode: response.statusCode, + stackTrace: stack); + } + } catch (e, st) { + return new AngelHttpException(error ?? e, + message: 'Unhandled exception while connecting to Angel backend.', + statusCode: response.statusCode, + stackTrace: stack ?? st); + } +} + +abstract class BaseAngelClient extends Angel { + @override + String authToken; + + final http.BaseClient client; + + BaseAngelClient(this.client, String basePath) : super(basePath); + + @override + Future authenticate( + {String type: auth_types.LOCAL, + credentials, + String authEndpoint: '/auth', + String reviveEndpoint: '/auth/token'}) async { + if (type == null) { + final url = '$basePath$reviveEndpoint'; + final response = await client.post(url, + headers: mergeMap([ + _writeHeaders, + {'Authorization': 'Bearer ${credentials['token']}'} + ])); + + try { + if (response.statusCode != 200) { + throw failure(response); + } + + final json = JSON.decode(response.body); + + if (json is! Map || + !json.containsKey('data') || + !json.containsKey('token')) { + throw new AngelHttpException.NotAuthenticated( + message: + "Auth endpoint '$url' did not return a proper response."); + } + + return new AngelAuthResult.fromMap(json); + } catch (e, st) { + throw failure(response, error: e, stack: st); + } + } else { + final url = '$basePath$authEndpoint/$type'; + http.Response response; + + if (credentials != null) { + response = await client.post(url, + body: JSON.encode(credentials), headers: _writeHeaders); + } else { + response = await client.post(url, headers: _writeHeaders); + } + + try { + if (response.statusCode != 200) { + throw failure(response); + } + + final json = JSON.decode(response.body); + + if (json is! Map || + !json.containsKey('data') || + !json.containsKey('token')) { + throw new AngelHttpException.NotAuthenticated( + message: + "Auth endpoint '$url' did not return a proper response."); + } + + return new AngelAuthResult.fromMap(json); + } catch (e, st) { + throw failure(response, error: e, stack: st); + } + } + } + + @override + Service service(String path, {Type type}) { + String uri = path.replaceAll(new RegExp(r"(^/)|(/+$)"), ""); + return new BaseAngelService(client, this, '$basePath/$uri'); + } +} + +class BaseAngelService extends Service { + @override + final Angel app; + final String basePath; + final http.BaseClient client; + + BaseAngelService(this.client, this.app, this.basePath); + + makeBody(x) { + return JSON.encode(x); + } + + /// Sends a non-streaming [Request] and returns a non-streaming [Response]. + Future sendUnstreamed( + String method, url, Map headers, + [body, Encoding encoding]) async { + if (url is String) url = Uri.parse(url); + var request = new http.Request(method, url); + + if (headers != null) request.headers.addAll(headers); + if (encoding != null) request.encoding = encoding; + if (body != null) { + if (body is String) { + request.body = body; + } else if (body is List) { + request.bodyBytes = DelegatingList.typed(body); + } else if (body is Map) { + request.bodyFields = DelegatingMap.typed(body); + } else { + throw new ArgumentError('Invalid request body "$body".'); + } + } + + return http.Response.fromStream(await client.send(request)); + } + + Future send(http.BaseRequest request) { + if (app.authToken != null && app.authToken.isNotEmpty) { + request.headers['Authorization'] = 'Bearer ${app.authToken}'; + } + + return client.send(request); + } + + @override + Future index([Map params]) async { + final response = await sendUnstreamed( + 'GET', '$basePath/${_buildQuery(params)}', _readHeaders); + + try { + if (response.statusCode != 200) { + throw failure(response); + } + + final json = JSON.decode(response.body); + + if (json is! List) { + throw failure(response); + } + + return json; + } catch (e, st) { + throw failure(response, error: e, stack: st); + } + } + + @override + Future read(id, [Map params]) async { + final response = await sendUnstreamed( + 'GET', '$basePath/$id${_buildQuery(params)}', _readHeaders); + + try { + if (response.statusCode != 200) { + throw failure(response); + } + + return JSON.decode(response.body); + } catch (e, st) { + throw failure(response, error: e, stack: st); + } + } + + @override + Future create(data, [Map params]) async { + final response = await sendUnstreamed( + 'POST', '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data)); + + try { + if (response.statusCode != 200) { + throw failure(response); + } + + return JSON.decode(response.body); + } catch (e, st) { + throw failure(response, error: e, stack: st); + } + } + + @override + Future modify(id, data, [Map params]) async { + final response = await sendUnstreamed( + 'PATCH', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); + + try { + if (response.statusCode != 200) { + throw failure(response); + } + + return JSON.decode(response.body); + } catch (e, st) { + throw failure(response, error: e, stack: st); + } + } + + @override + Future update(id, data, [Map params]) async { + final response = await sendUnstreamed( + 'POST', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); + + try { + if (response.statusCode != 200) { + throw failure(response); + } + + return JSON.decode(response.body); + } catch (e, st) { + throw failure(response, error: e, stack: st); + } + } + + @override + Future remove(id, [Map params]) async { + final response = await sendUnstreamed( + 'DELETE', '$basePath/$id${_buildQuery(params)}', _readHeaders); + + try { + if (response.statusCode != 200) { + throw failure(response); + } + + return JSON.decode(response.body); + } catch (e, st) { + throw failure(response, error: e, stack: st); + } + } +} diff --git a/lib/browser.dart b/lib/browser.dart index 605ed677..e112f20c 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -1,54 +1,22 @@ /// Browser library for the Angel framework. library angel_client.browser; -import 'dart:async' show Completer, Future; +import 'dart:async' show Future; import 'dart:convert' show JSON; -import 'dart:html' show HttpRequest, window; +import 'dart:html' show window; +import 'package:http/browser_client.dart' as http; import 'angel_client.dart'; import 'auth_types.dart' as auth_types; +import 'base_angel_client.dart'; export 'angel_client.dart'; -_buildQuery(Map params) { - if (params == null || params == {}) return ""; - - String result = ""; - return result; -} - -_send(HttpRequest request, [data]) { - final completer = new Completer(); - - 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); - } - }); - - if (data == null) - request.send(); - else if (data is String) - request.send(data); - else - request.send(JSON.encode(data)); - return completer.future; -} - /// Queries an Angel server via REST. -class Rest extends Angel { - String _authToken; - - Rest(String basePath) : super(basePath); +class Rest extends BaseAngelClient { + Rest(String basePath) : super(new http.BrowserClient(), basePath); @override - Future authenticate( - {String type, + Future authenticate( + {String type: auth_types.LOCAL, credentials, String authEndpoint: '/auth', String reviveEndpoint: '/auth/token'}) async { @@ -58,177 +26,23 @@ class Rest extends Angel { 'Cannot revive token from localStorage - there is none.'); } - final result = new _AngelAuthResultImpl( - token: JSON.decode(window.localStorage['token']), - data: JSON.decode(window.localStorage['user'])); - final completer = new Completer(); - final request = new HttpRequest()..responseType = 'json'; - request.open('POST', '$basePath$reviveEndpoint'); - request.setRequestHeader('Accept', 'application/json'); - request.setRequestHeader('Content-Type', 'application/json'); - 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); - } - }); - - request.send(JSON.encode(result)); - return completer.future; - } - - final url = '$basePath$authEndpoint/$type'; - - 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((_) { - 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); - } - }); - - if (credentials == null) - request.send(); - else - request.send(JSON.encode(credentials)); - - return completer.future; + try { + final result = await super.authenticate( + credentials: {'token': JSON.decode(window.localStorage['token'])}, + reviveEndpoint: reviveEndpoint); + window.localStorage['token'] = JSON.encode(authToken = result.token); + window.localStorage['user'] = JSON.encode(result.data); + return result; + } catch (e, st) { + throw new AngelHttpException(e, + message: 'Failed to revive auth token.', stackTrace: st); + } } else { - throw new Exception('angel_client cannot authenticate as "$type" yet.'); + final result = await super.authenticate( + type: type, credentials: credentials, authEndpoint: authEndpoint); + window.localStorage['token'] = JSON.encode(authToken = result.token); + window.localStorage['user'] = JSON.encode(result.data); + return result; } } - - @override - RestService service(String path, {Type type}) { - String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), ""); - return new _RestServiceImpl(this, "$basePath/$uri"); - } -} - -abstract class RestService extends Service { - RestService._(String basePath); -} - -class _AngelAuthResultImpl implements AngelAuthResult { - String _token; - final Map data = {}; - String get token => _token; - - _AngelAuthResultImpl({token, Map data: const {}}) { - if (token is String) _token = token; - - this.data.addAll(data ?? {}); - } - - 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 toJson() { - return {'token': token, 'data': data}; - } -} - -/// Queries an Angel service via REST. -class _RestServiceImpl extends RestService { - final Rest app; - String _basePath; - String get basePath => _basePath; - - _RestServiceImpl(this.app, String basePath) : super._(basePath) { - _basePath = basePath; - } - - _makeBody(data) { - return JSON.encode(data); - } - - Future buildRequest(String url, - {String method: "POST", bool write: true}) async { - HttpRequest request = new HttpRequest(); - request.open(method, url); - request.responseType = "json"; - request.setRequestHeader("Accept", "application/json"); - if (write) request.setRequestHeader("Content-Type", "application/json"); - if (app._authToken != null) - request.setRequestHeader("Authorization", "Bearer ${app._authToken}"); - return request; - } - - @override - Future index([Map params]) async { - final request = await buildRequest('$basePath/${_buildQuery(params)}', - method: 'GET', write: false); - return await _send(request); - } - - @override - Future read(id, [Map params]) async { - final request = await buildRequest('$basePath/$id${_buildQuery(params)}', - method: 'GET', write: false); - return await _send(request); - } - - @override - Future create(data, [Map params]) async { - final request = await buildRequest("$basePath/${_buildQuery(params)}"); - return await _send(request, _makeBody(data)); - } - - @override - Future modify(id, data, [Map params]) async { - final request = await buildRequest("$basePath/$id${_buildQuery(params)}", - method: "PATCH"); - return await _send(request, _makeBody(data)); - } - - @override - Future update(id, data, [Map params]) async { - final request = await buildRequest("$basePath/$id${_buildQuery(params)}"); - return await _send(request, _makeBody(data)); - } - - @override - Future remove(id, [Map params]) async { - final request = await buildRequest("$basePath/$id${_buildQuery(params)}", - method: "DELETE"); - return await _send(request); - } } diff --git a/lib/io.dart b/lib/io.dart index cf72778c..0e544e7e 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -2,109 +2,68 @@ library angel_client.cli; import 'dart:async'; -import 'dart:convert' show JSON; -import 'package:http/http.dart'; +import 'package:http/http.dart' as http; import 'package:json_god/json_god.dart' as god; import 'angel_client.dart'; +import 'base_angel_client.dart'; export 'angel_client.dart'; -_buildQuery(Map params) { - if (params == null || params == {}) - return ""; - - String result = ""; - return result; -} - -const Map _readHeaders = const { - "Accept": "application/json" -}; - -const Map _writeHeaders = const { - "Accept": "application/json", - "Content-Type": "application/json" -}; - /// Queries an Angel server via REST. -class Rest extends Angel { - BaseClient client; - - Rest(String path, BaseClient this.client) :super(path); +class Rest extends BaseAngelClient { + Rest(String path) : super(new http.Client(), path); @override - RestService service(String path, {Type type}) { - String uri = path.replaceAll(new RegExp(r"(^\/)|(\/+$)"), ""); - return new RestService._base("$basePath/$uri", client, type) - ..app = this; + Service service(String path, {Type type}) { + String uri = path.replaceAll(new RegExp(r"(^/)|(/+$)"), ""); + return new RestService(client, this, "$basePath/$uri", type); } } /// Queries an Angel service via REST. -class RestService extends Service { - String basePath; - BaseClient client; - Type outputType; +class RestService extends BaseAngelService { + final Type type; - RestService._base(Pattern path, BaseClient this.client, - Type this.outputType) { - this.basePath = (path is RegExp) ? path.pattern : path; + RestService(http.BaseClient client, Angel app, String url, this.type) + : super(client, app, url); + + deserialize(x) { + if (type != null) { + return god.deserializeDatum(x, outputType: type); + } + + return x; } - _makeBody(data) { - if (outputType == null) - return JSON.encode(data); - else return god.serialize(data); + @override + makeBody(x) { + if (type != null) { + return super.makeBody(god.serializeObject(x)); + } + + return super.makeBody(x); } @override Future index([Map params]) async { - var response = await client.get( - "$basePath/${_buildQuery(params)}", headers: _readHeaders); - - if (outputType == null) - return god.deserialize(response.body); - - else { - return JSON.decode(response.body).map((x) => - god.deserializeDatum(x, outputType: outputType)).toList(); - } + final items = await super.index(params); + return items.map(deserialize).toList(); } @override - Future read(id, [Map params]) async { - var response = await client.get( - "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); - return god.deserialize(response.body, outputType: outputType); - } + Future read(id, [Map params]) => super.read(id, params).then(deserialize); @override - Future create(data, [Map params]) async { - var response = await client.post( - "$basePath/${_buildQuery(params)}", body: _makeBody(data), - headers: _writeHeaders); - return god.deserialize(response.body, outputType: outputType); - } + Future create(data, [Map params]) => + super.create(data, params).then(deserialize); @override - Future modify(id, data, [Map params]) async { - var response = await client.patch( - "$basePath/$id${_buildQuery(params)}", body: _makeBody(data), - headers: _writeHeaders); - return god.deserialize(response.body, outputType: outputType); - } + Future modify(id, data, [Map params]) => + super.modify(id, data, params).then(deserialize); @override - Future update(id, data, [Map params]) async { - var response = await client.patch( - "$basePath/$id${_buildQuery(params)}", body: _makeBody(data), - headers: _writeHeaders); - return god.deserialize(response.body, outputType: outputType); - } + Future update(id, data, [Map params]) => + super.update(id, data, params).then(deserialize); @override - Future remove(id, [Map params]) async { - var response = await client.delete( - "$basePath/$id${_buildQuery(params)}", headers: _readHeaders); - return god.deserialize(response.body, outputType: outputType); - } + Future remove(id, [Map params]) => super.remove(id, params).then(deserialize); } diff --git a/pubspec.yaml b/pubspec.yaml index d9b1ce82..d0e032d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,12 @@ name: angel_client -version: 1.0.0-dev+15 +version: 1.0.0-dev+16 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client dependencies: + angel_framework: ">=1.0.0-dev <2.0.0" http: ">= 0.11.3 < 0.12.0" json_god: ">=2.0.0-beta <3.0.0" merge_map: ">=1.0.0 <2.0.0" dev_dependencies: - angel_framework: ">=1.0.0-dev <2.0.0" test: ">= 0.12.13 < 0.13.0" diff --git a/test/io_test.dart b/test/io_test.dart index 894a3399..d804a485 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -22,7 +22,7 @@ main() { serverApp.use("/postcards", new server.MemoryService()); serverPostcards = serverApp.service("postcards"); - clientApp = new client.Rest(url, new http.Client()); + clientApp = new client.Rest(url); clientPostcards = clientApp.service("postcards"); clientTypedPostcards = clientApp.service("postcards", type: Postcard); }); From b0c5f16730b784eb13e12727a5eb1b2204e7d4a4 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 8 Dec 2016 19:25:18 -0500 Subject: [PATCH 18/69] README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84e85f9e..14a9ef28 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0-dev+16](https://img.shields.io/badge/pub-1.0.0--dev+16-red.svg)](https://pub.dartlang.org/packages/angel_framework) +[![pub 1.0.0-dev+16](https://img.shields.io/badge/pub-1.0.0--dev+16-red.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. From 59a4a22774aeafd217dcc37ec7ec154c4ce04dc2 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 10 Dec 2016 09:45:22 -0500 Subject: [PATCH 19/69] +17 --- README.md | 4 ++- lib/angel_client.dart | 2 +- lib/base_angel_client.dart | 57 ++++++++++++++++++++++++++++++++------ lib/io.dart | 7 +++-- pubspec.yaml | 4 ++- test/io_test.dart | 3 +- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 14a9ef28..9736cd33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0-dev+16](https://img.shields.io/badge/pub-1.0.0--dev+16-red.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.0-dev+17](https://img.shields.io/badge/pub-1.0.0--dev+17-red.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. @@ -44,6 +44,8 @@ class Todo extends Model { } bar() async { + // By the next release, this will just be: + // app.service("todos") Service Todos = app.service("todos", type: Todo); List todos = await Todos.index(); diff --git a/lib/angel_client.dart b/lib/angel_client.dart index b603c430..ba6f3e6e 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -26,7 +26,7 @@ abstract class Angel { await configurer(this); } - Service service(Pattern path, {Type type}); + Service service(Pattern path, {Type type}); } /// Represents the result of authentication with an Angel server. diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index ff6f17e7..f6d0a5f8 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -11,6 +11,7 @@ import 'package:merge_map/merge_map.dart'; import 'angel_client.dart'; import 'auth_types.dart' as auth_types; +final RegExp straySlashes = new RegExp(r"(^/)|(/+$)"); const Map _readHeaders = const {'Accept': 'application/json'}; final Map _writeHeaders = mergeMap([ _readHeaders, @@ -23,7 +24,7 @@ _buildQuery(Map params) { List query = []; params.forEach((k, v) { - query.add('$k=$v'); + query.add('$k=${Uri.encodeQueryComponent(v.toString())}'); }); return '?' + query.join('&'); @@ -124,8 +125,8 @@ abstract class BaseAngelClient extends Angel { } @override - Service service(String path, {Type type}) { - String uri = path.replaceAll(new RegExp(r"(^/)|(/+$)"), ""); + Service service(String path, {Type type}) { + String uri = path.replaceAll(straySlashes, ""); return new BaseAngelService(client, this, '$basePath/$uri'); } } @@ -166,6 +167,16 @@ class BaseAngelService extends Service { return http.Response.fromStream(await client.send(request)); } + String _join(url) { + final head = basePath.replaceAll(new RegExp(r'/+$'), ''); + final tail = basePath.replaceAll(straySlashes, ''); + return '$head/$tail'; + } + + Future close() async { + client.close(); + } + Future send(http.BaseRequest request) { if (app.authToken != null && app.authToken.isNotEmpty) { request.headers['Authorization'] = 'Bearer ${app.authToken}'; @@ -174,6 +185,34 @@ class BaseAngelService extends Service { return client.send(request); } + Future delete(String url, + {Map headers}) async { + return client.delete(_join(url), headers: headers); + } + + Future get(String url, {Map headers}) async { + return client.get(_join(url), headers: headers); + } + + Future head(String url, {Map headers}) async { + return client.head(_join(url), headers: headers); + } + + Future patch(String url, + {body, Map headers}) async { + return client.patch(_join(url), body: body, headers: headers); + } + + Future post(String url, + {body, Map headers}) async { + return client.post(_join(url), body: body, headers: headers); + } + + Future put(String url, + {body, Map headers}) async { + return client.put(_join(url), body: body, headers: headers); + } + @override Future index([Map params]) async { final response = await sendUnstreamed( @@ -214,8 +253,8 @@ class BaseAngelService extends Service { @override Future create(data, [Map params]) async { - final response = await sendUnstreamed( - 'POST', '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data)); + final response = await sendUnstreamed('POST', + '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { if (response.statusCode != 200) { @@ -230,8 +269,8 @@ class BaseAngelService extends Service { @override Future modify(id, data, [Map params]) async { - final response = await sendUnstreamed( - 'PATCH', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); + final response = await sendUnstreamed('PATCH', + '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { if (response.statusCode != 200) { @@ -246,8 +285,8 @@ class BaseAngelService extends Service { @override Future update(id, data, [Map params]) async { - final response = await sendUnstreamed( - 'POST', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); + final response = await sendUnstreamed('POST', + '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { if (response.statusCode != 200) { diff --git a/lib/io.dart b/lib/io.dart index 0e544e7e..9bd8d80d 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -13,9 +13,10 @@ class Rest extends BaseAngelClient { Rest(String path) : super(new http.Client(), path); @override - Service service(String path, {Type type}) { - String uri = path.replaceAll(new RegExp(r"(^/)|(/+$)"), ""); - return new RestService(client, this, "$basePath/$uri", type); + Service service(String path, {Type type}) { + String uri = path.replaceAll(straySlashes, ""); + return new RestService( + client, this, "$basePath/$uri", T != dynamic ? T : type); } } diff --git a/pubspec.yaml b/pubspec.yaml index d0e032d7..962ceef9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,10 @@ name: angel_client -version: 1.0.0-dev+16 +version: 1.0.0-dev+17 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client +environment: + sdk: ">=1.21.0" dependencies: angel_framework: ">=1.0.0-dev <2.0.0" http: ">= 0.11.3 < 0.12.0" diff --git a/test/io_test.dart b/test/io_test.dart index d804a485..a54fd802 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:angel_client/io.dart' as client; import 'package:angel_framework/angel_framework.dart' as server; -import 'package:http/http.dart' as http; import 'package:json_god/json_god.dart' as god; import 'package:test/test.dart'; import 'shared.dart'; @@ -24,7 +23,7 @@ main() { clientApp = new client.Rest(url); clientPostcards = clientApp.service("postcards"); - clientTypedPostcards = clientApp.service("postcards", type: Postcard); + clientTypedPostcards = clientApp.service("postcards", type: Postcard); }); tearDown(() async { From a72c036dd13c65362d2563573993036074ae795e Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 10 Dec 2016 09:50:05 -0500 Subject: [PATCH 20/69] +18 --- README.md | 2 +- lib/angel_client.dart | 2 ++ lib/base_angel_client.dart | 8 ++++---- pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9736cd33..4541484f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0-dev+17](https://img.shields.io/badge/pub-1.0.0--dev+17-red.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.0-dev+18](https://img.shields.io/badge/pub-1.0.0--dev+18-red.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index ba6f3e6e..61cbbfb3 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -21,6 +21,8 @@ abstract class Angel { String authEndpoint: '/auth', String reviveEndpoint: '/auth/token'}); + Future close(); + /// Applies an [AngelConfigurer] to this instance. Future configure(AngelConfigurer configurer) async { await configurer(this); diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index f6d0a5f8..14255033 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -124,6 +124,10 @@ abstract class BaseAngelClient extends Angel { } } + Future close() async { + client.close(); + } + @override Service service(String path, {Type type}) { String uri = path.replaceAll(straySlashes, ""); @@ -173,10 +177,6 @@ class BaseAngelService extends Service { return '$head/$tail'; } - Future close() async { - client.close(); - } - Future send(http.BaseRequest request) { if (app.authToken != null && app.authToken.isNotEmpty) { request.headers['Authorization'] = 'Bearer ${app.authToken}'; diff --git a/pubspec.yaml b/pubspec.yaml index 962ceef9..11193d77 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+17 +version: 1.0.0-dev+18 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 41a73d0b20fb9d3076b3a871e6a7a031ce54b1b5 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 10 Dec 2016 12:15:54 -0500 Subject: [PATCH 21/69] +19 --- README.md | 2 +- lib/angel_client.dart | 17 +++++++++ lib/base_angel_client.dart | 74 ++++++++++++++++++++------------------ pubspec.yaml | 2 +- 4 files changed, 59 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 4541484f..046205e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0-dev+18](https://img.shields.io/badge/pub-1.0.0--dev+18-red.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.0-dev+19](https://img.shields.io/badge/pub-1.0.0--dev+19-red.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 61cbbfb3..8ad4eb6f 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -3,6 +3,7 @@ library angel_client; import 'dart:async'; import 'dart:convert'; +import 'package:http/src/response.dart' as http; export 'package:angel_framework/src/http/angel_http_exception.dart'; /// A function that configures an [Angel] client in some way. @@ -29,6 +30,22 @@ abstract class Angel { } Service service(Pattern path, {Type type}); + + Future delete(String url, + {Map headers}); + + Future get(String url, {Map headers}); + + Future head(String url, {Map headers}); + + Future patch(String url, + {body, Map headers}); + + Future post(String url, + {body, Map headers}); + + Future put(String url, + {body, Map headers}); } /// Represents the result of authentication with an Angel server. diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 14255033..a6a105b4 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -133,6 +133,46 @@ abstract class BaseAngelClient extends Angel { String uri = path.replaceAll(straySlashes, ""); return new BaseAngelService(client, this, '$basePath/$uri'); } + + String _join(url) { + final head = basePath.replaceAll(new RegExp(r'/+$'), ''); + final tail = basePath.replaceAll(straySlashes, ''); + return '$head/$tail'; + } + + @override + Future delete(String url, + {Map headers}) async { + return client.delete(_join(url), headers: headers); + } + + @override + Future get(String url, {Map headers}) async { + return client.get(_join(url), headers: headers); + } + + @override + Future head(String url, {Map headers}) async { + return client.head(_join(url), headers: headers); + } + + @override + Future patch(String url, + {body, Map headers}) async { + return client.patch(_join(url), body: body, headers: headers); + } + + @override + Future post(String url, + {body, Map headers}) async { + return client.post(_join(url), body: body, headers: headers); + } + + @override + Future put(String url, + {body, Map headers}) async { + return client.put(_join(url), body: body, headers: headers); + } } class BaseAngelService extends Service { @@ -171,12 +211,6 @@ class BaseAngelService extends Service { return http.Response.fromStream(await client.send(request)); } - String _join(url) { - final head = basePath.replaceAll(new RegExp(r'/+$'), ''); - final tail = basePath.replaceAll(straySlashes, ''); - return '$head/$tail'; - } - Future send(http.BaseRequest request) { if (app.authToken != null && app.authToken.isNotEmpty) { request.headers['Authorization'] = 'Bearer ${app.authToken}'; @@ -185,34 +219,6 @@ class BaseAngelService extends Service { return client.send(request); } - Future delete(String url, - {Map headers}) async { - return client.delete(_join(url), headers: headers); - } - - Future get(String url, {Map headers}) async { - return client.get(_join(url), headers: headers); - } - - Future head(String url, {Map headers}) async { - return client.head(_join(url), headers: headers); - } - - Future patch(String url, - {body, Map headers}) async { - return client.patch(_join(url), body: body, headers: headers); - } - - Future post(String url, - {body, Map headers}) async { - return client.post(_join(url), body: body, headers: headers); - } - - Future put(String url, - {body, Map headers}) async { - return client.put(_join(url), body: body, headers: headers); - } - @override Future index([Map params]) async { final response = await sendUnstreamed( diff --git a/pubspec.yaml b/pubspec.yaml index 11193d77..ba0db73f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+18 +version: 1.0.0-dev+19 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 269b0483dbf5f0dd39c13cb006b450c1ded8e73d Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 10 Dec 2016 12:28:24 -0500 Subject: [PATCH 22/69] +20 --- README.md | 2 +- lib/base_angel_client.dart | 2 +- pubspec.yaml | 2 +- test/io_test.dart | 6 ++++++ test/shared.dart | 12 +++++++----- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 046205e9..0f8cd15b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0-dev+19](https://img.shields.io/badge/pub-1.0.0--dev+19-red.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.0-dev+20](https://img.shields.io/badge/pub-1.0.0--dev+20-red.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index a6a105b4..2b2426f2 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -136,7 +136,7 @@ abstract class BaseAngelClient extends Angel { String _join(url) { final head = basePath.replaceAll(new RegExp(r'/+$'), ''); - final tail = basePath.replaceAll(straySlashes, ''); + final tail = url.replaceAll(straySlashes, ''); return '$head/$tail'; } diff --git a/pubspec.yaml b/pubspec.yaml index ba0db73f..ef29353c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+19 +version: 1.0.0-dev+20 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/io_test.dart b/test/io_test.dart index a54fd802..4de6c9fc 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -30,9 +30,15 @@ main() { await httpServer.close(force: true); }); + test('plain requests', () async { + final response = await clientApp.get('/foo'); + print(response.body); + }); + test("index", () async { Postcard niagaraFalls = await serverPostcards.create( new Postcard(location: "Niagara Falls", message: "Missing you!")); + print('Niagra Falls: ${niagaraFalls.toJson()}'); List indexed = await clientPostcards.index(); print(indexed); diff --git a/test/shared.dart b/test/shared.dart index d9d9a551..4a5e11a0 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -9,12 +9,14 @@ class Postcard extends MemoryModel { @override bool operator ==(other) { - if (!(other is Postcard)) - return false; + if (!(other is Postcard)) return false; - return id == other.id && location == other.location && + return id == other.id && + location == other.location && message == other.message; } - -} \ No newline at end of file + Map toJson() { + return {'id': id, 'location': location, 'message': message}; + } +} From ec67091b10c8f1b457a68e1ce973cc17c97681b2 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 13 Dec 2016 11:34:22 -0500 Subject: [PATCH 23/69] Nearly --- .babelrc | 0 .gitignore | 5 ++- .npmignore | 79 ++++++++++++++++++++++++++++++++++++++ analysis_options.yaml | 4 ++ lib/angel_client.dart | 2 +- lib/base_angel_client.dart | 21 +++++----- package.json | 26 +++++++++++++ test/shared.dart | 1 - 8 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 .babelrc create mode 100644 .npmignore create mode 100644 analysis_options.yaml create mode 100644 package.json diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..e69de29b diff --git a/.gitignore b/.gitignore index ea89ccf0..e67c11e2 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,7 @@ doc/api/ # Don't commit pubspec lock file # (Library packages only! Remove pattern if developing an application package) pubspec.lock -.idea \ No newline at end of file +.idea + +lib/angel_client.js +*.sum \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..82e61f78 --- /dev/null +++ b/.npmignore @@ -0,0 +1,79 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml + +# Sensitive or high-churn files: +.idea/dataSources/ +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Dart template +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub + +# SDK 1.20 and later (no longer creates packages directories) +.packages +.pub/ +build/ + +# Older SDK versions +# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) +.project +.buildlog +**/packages/ + + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +pubspec.lock + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..1325f758 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +analyzer: + strong-mode: true + exclude: + - test/io_test.dart \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 8ad4eb6f..d56816b3 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -29,7 +29,7 @@ abstract class Angel { await configurer(this); } - Service service(Pattern path, {Type type}); + Service service(String path, {Type type}); Future delete(String url, {Map headers}); diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 2b2426f2..191df0bd 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -7,16 +7,15 @@ 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:merge_map/merge_map.dart'; import 'angel_client.dart'; import 'auth_types.dart' as auth_types; final RegExp straySlashes = new RegExp(r"(^/)|(/+$)"); const Map _readHeaders = const {'Accept': 'application/json'}; -final Map _writeHeaders = mergeMap([ - _readHeaders, - const {'Content-Type': 'application/json'} -]); +const Map _writeHeaders = const { + 'Accept': 'application/json', + 'Content-Type': 'application/json' +}; _buildQuery(Map params) { if (params == null || params.isEmpty) return ""; @@ -66,11 +65,11 @@ abstract class BaseAngelClient extends Angel { String reviveEndpoint: '/auth/token'}) async { if (type == null) { final url = '$basePath$reviveEndpoint'; - final response = await client.post(url, - headers: mergeMap([ - _writeHeaders, - {'Authorization': 'Bearer ${credentials['token']}'} - ])); + final response = await client.post(url, headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ${credentials['token']}' + }); try { if (response.statusCode != 200) { @@ -130,7 +129,7 @@ abstract class BaseAngelClient extends Angel { @override Service service(String path, {Type type}) { - String uri = path.replaceAll(straySlashes, ""); + String uri = path.toString().replaceAll(straySlashes, ""); return new BaseAngelService(client, this, '$basePath/$uri'); } diff --git a/package.json b/package.json new file mode 100644 index 00000000..1bd640b7 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "angel_client", + "version": "1.0.0-dev", + "description": "Client library for the Angel framework.", + "main": "lib/angel_client.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/angel-dart/angel_client.git" + }, + "keywords": [ + "angel", + "angel_client" + ], + "author": "Tobe O ", + "license": "MIT", + "bugs": { + "url": "https://github.com/angel-dart/angel_client/issues" + }, + "homepage": "https://github.com/angel-dart/angel_client#readme" +} diff --git a/test/shared.dart b/test/shared.dart index 4a5e11a0..59031362 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -1,7 +1,6 @@ import "package:angel_framework/src/defs.dart"; class Postcard extends MemoryModel { - int id; String location; String message; From c700a399e4fe82eba63bcb40e06987e41592c31d Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 13 Dec 2016 11:35:35 -0500 Subject: [PATCH 24/69] More --- .babelrc | 4 ++ .gitignore | 50 ++++++++++++++++++++- .npmignore | 89 +++++--------------------------------- README.md | 2 +- lib/angel_client.dart | 8 +++- lib/base_angel_client.dart | 24 ++++++---- lib/io.dart | 2 +- package.json | 13 +++++- pubspec.yaml | 2 +- test/io_test.dart | 2 +- 10 files changed, 100 insertions(+), 96 deletions(-) diff --git a/.babelrc b/.babelrc index e69de29b..c56a9bf9 100644 --- a/.babelrc +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015"], + "plugins": ["add-module-exports"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e67c11e2..a84c50a4 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,52 @@ pubspec.lock .idea lib/angel_client.js -*.sum \ No newline at end of file +*.sum + +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity diff --git a/.npmignore b/.npmignore index 82e61f78..754250b1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,79 +1,10 @@ -# Created by .ignore support plugin (hsz.mobi) -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: -.idea/workspace.xml -.idea/tasks.xml - -# Sensitive or high-churn files: -.idea/dataSources/ -.idea/dataSources.ids -.idea/dataSources.xml -.idea/dataSources.local.xml -.idea/sqlDataSources.xml -.idea/dynamic.xml -.idea/uiDesigner.xml - -# Gradle: -.idea/gradle.xml -.idea/libraries - -# Mongo Explorer plugin: -.idea/mongoSettings.xml - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties -### Dart template -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub - -# SDK 1.20 and later (no longer creates packages directories) -.packages -.pub/ -build/ - -# Older SDK versions -# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) -.project -.buildlog -**/packages/ - - -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) -*.dart.js -*.part.js -*.js.deps -*.js.map -*.info.json - -# Directory created by dartdoc -doc/api/ - -# Don't commit pubspec lock file -# (Library packages only! Remove pattern if developing an application package) -pubspec.lock - +.babelrc +.istanbul.yml +.travis.yml +.editorconfig +.idea/ +src/ +test/ +!lib/ +.github/ +coverage \ No newline at end of file diff --git a/README.md b/README.md index 0f8cd15b..4c1a36ec 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0-dev+20](https://img.shields.io/badge/pub-1.0.0--dev+20-red.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.0-dev+21](https://img.shields.io/badge/pub-1.0.0--dev+21-red.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index d56816b3..0915bdb8 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -9,6 +9,12 @@ export 'package:angel_framework/src/http/angel_http_exception.dart'; /// A function that configures an [Angel] client in some way. typedef Future AngelConfigurer(Angel app); +/// A function that deserializes data received from the server. +/// +/// This is only really necessary in the browser, where `json_god` +/// doesn't work. +typedef AngelDeserializer(x); + /// Represents an Angel server that we are querying. abstract class Angel { String get authToken; @@ -29,7 +35,7 @@ abstract class Angel { await configurer(this); } - Service service(String path, {Type type}); + Service service(String path, {Type type, AngelDeserializer deserializer}); Future delete(String url, {Map headers}); diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 191df0bd..d44a1ee4 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -128,9 +128,10 @@ abstract class BaseAngelClient extends Angel { } @override - Service service(String path, {Type type}) { + Service service(String path, {Type type, AngelDeserializer deserializer}) { String uri = path.toString().replaceAll(straySlashes, ""); - return new BaseAngelService(client, this, '$basePath/$uri'); + return new BaseAngelService(client, this, '$basePath/$uri', + deserializer: deserializer); } String _join(url) { @@ -179,8 +180,13 @@ class BaseAngelService extends Service { final Angel app; final String basePath; final http.BaseClient client; + final AngelDeserializer deserializer; - BaseAngelService(this.client, this.app, this.basePath); + BaseAngelService(this.client, this.app, this.basePath, {this.deserializer}); + + deserialize(x) { + return deserializer != null ? deserializer(x) : x; + } makeBody(x) { return JSON.encode(x); @@ -234,7 +240,7 @@ class BaseAngelService extends Service { throw failure(response); } - return json; + return json.map(deserialize).toList(); } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -250,7 +256,7 @@ class BaseAngelService extends Service { throw failure(response); } - return JSON.decode(response.body); + return deserialize(JSON.decode(response.body)); } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -266,7 +272,7 @@ class BaseAngelService extends Service { throw failure(response); } - return JSON.decode(response.body); + return deserialize(JSON.decode(response.body)); } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -282,7 +288,7 @@ class BaseAngelService extends Service { throw failure(response); } - return JSON.decode(response.body); + return deserialize(JSON.decode(response.body)); } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -298,7 +304,7 @@ class BaseAngelService extends Service { throw failure(response); } - return JSON.decode(response.body); + return deserialize(JSON.decode(response.body)); } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -314,7 +320,7 @@ class BaseAngelService extends Service { throw failure(response); } - return JSON.decode(response.body); + return deserialize(JSON.decode(response.body)); } catch (e, st) { throw failure(response, error: e, stack: st); } diff --git a/lib/io.dart b/lib/io.dart index 9bd8d80d..24841479 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -13,7 +13,7 @@ class Rest extends BaseAngelClient { Rest(String path) : super(new http.Client(), path); @override - Service service(String path, {Type type}) { + Service service(String path, {Type type, AngelDeserializer deserializer}) { String uri = path.replaceAll(straySlashes, ""); return new RestService( client, this, "$basePath/$uri", T != dynamic ? T : type); diff --git a/package.json b/package.json index 1bd640b7..70867bee 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,15 @@ "name": "angel_client", "version": "1.0.0-dev", "description": "Client library for the Angel framework.", - "main": "lib/angel_client.js", + "main": "build/angel_client.js", + "jsnext:main": "lib/angel_client.js", "directories": { "test": "test" }, "scripts": { + "compile": "npm run dartdevc && babel -o build/angel_client.js lib/angel_client.js", + "dartdevc": "dartdevc --modules node -o lib/angel_client.js lib/angel_client.dart", + "prepublish": "npm run compile", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -22,5 +26,10 @@ "bugs": { "url": "https://github.com/angel-dart/angel_client/issues" }, - "homepage": "https://github.com/angel-dart/angel_client#readme" + "homepage": "https://github.com/angel-dart/angel_client#readme", + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-preset-es2015": "^6.18.0" + } } diff --git a/pubspec.yaml b/pubspec.yaml index ef29353c..8669f398 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+20 +version: 1.0.0-dev+21 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/io_test.dart b/test/io_test.dart index 4de6c9fc..158af35e 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -23,7 +23,7 @@ main() { clientApp = new client.Rest(url); clientPostcards = clientApp.service("postcards"); - clientTypedPostcards = clientApp.service("postcards", type: Postcard); + clientTypedPostcards = clientApp.service("postcards", type: Postcard); }); tearDown(() async { From e7e711340bcc474fe2c8dddf9e193e85c92c7b94 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Fri, 20 Jan 2017 18:57:42 -0500 Subject: [PATCH 25/69] Broken --- lib/base_angel_client.dart | 6 +++++- test/browser.dart | 11 ----------- test/browser_test.dart | 37 +++++++++++++++++++++++++++++++++++++ test/for_browser_tests.dart | 21 +++++++++++++++------ 4 files changed, 57 insertions(+), 18 deletions(-) delete mode 100644 test/browser.dart create mode 100644 test/browser_test.dart diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index d44a1ee4..1614b397 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -81,7 +81,7 @@ abstract class BaseAngelClient extends Angel { if (json is! Map || !json.containsKey('data') || !json.containsKey('token')) { - throw new AngelHttpException.NotAuthenticated( + throw new AngelHttpException.notAuthenticated( message: "Auth endpoint '$url' did not return a proper response."); } @@ -200,6 +200,10 @@ class BaseAngelService extends Service { var request = new http.Request(method, url); if (headers != null) request.headers.addAll(headers); + + if (app.authToken?.isNotEmpty == true) + request.headers['Authorization'] = 'Bearer ${app.authToken}'; + if (encoding != null) request.encoding = encoding; if (body != null) { if (body is String) { diff --git a/test/browser.dart b/test/browser.dart deleted file mode 100644 index 5eb50ac0..00000000 --- a/test/browser.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:angel_client/browser.dart'; -import 'package:test/test.dart'; - -main() async { - test("list todos", () async { - Angel app = new Rest("http://localhost:3001"); - Service Todos = app.service("todos"); - - print(await Todos.index()); - }); -} diff --git a/test/browser_test.dart b/test/browser_test.dart new file mode 100644 index 00000000..8eb28db9 --- /dev/null +++ b/test/browser_test.dart @@ -0,0 +1,37 @@ +@TestOn('browser') +import 'package:angel_client/browser.dart'; +import 'package:test/test.dart'; +import 'for_browser_tests.dart'; + +main() { + test("list todos", () async { + var channel = spawnHybridCode(SERVER); + int port = await channel.stream.first; + var url = "http://localhost:$port"; + print(url); + var app = new Rest(url); + var todoService = app.service("todos"); + + var todos = await todoService.index(); + expect(todos, isEmpty); + }); + + test('create todos', () async { + var channel = spawnHybridCode(SERVER); + int port = await channel.stream.first; + var url = "http://localhost:$port"; + print(url); + var app = new Rest(url); + var todoService = app.service("todos"); + + var data = {'hello': 'world'}; + var response = await todoService.create(data); + print('Created response: $response'); + + var todos = await todoService.index(); + expect(todos, hasLength(1)); + + Map todo = todos.first; + expect(todo, equals(data)); + }); +} diff --git a/test/for_browser_tests.dart b/test/for_browser_tests.dart index d020dc92..e23cb79a 100644 --- a/test/for_browser_tests.dart +++ b/test/for_browser_tests.dart @@ -1,21 +1,30 @@ +const String SERVER = ''' import 'dart:io'; import "package:angel_framework/angel_framework.dart"; import "package:angel_framework/src/defs.dart"; +import 'package:stream_channel/stream_channel.dart'; + +hybridMain(StreamChannel channel) async { + var app = new Angel(); -main() async { - Angel app = new Angel(); app.before.add((req, ResponseContext res) { - res.header("Access-Control-Allow-Origin", "*"); + res.headers["Access-Control-Allow-Origin"] = "*"; + return true; }); app.use("/todos", new MemoryService()); - await app.startServer(InternetAddress.LOOPBACK_IP_V4, 3001); - print("Server up on localhost:3001"); + var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + + print("Server up; listening at http://localhost:\${server.port}"); + channel.sink.add(server.port); } class Todo extends MemoryModel { String hello; - Todo({String this.hello}); + Todo({int id, this.hello}) { + this.id = id; + } } +'''; From 1984839af5c236734e27aa74f895999a81917cb7 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 25 Jan 2017 18:25:31 -0500 Subject: [PATCH 26/69] IO side good --- .travis.yml | 3 +- lib/base_angel_client.dart | 84 +++++++++++++++++++------------------- lib/io.dart | 4 +- test/io_test.dart | 33 ++++++++------- 4 files changed, 66 insertions(+), 58 deletions(-) diff --git a/.travis.yml b/.travis.yml index de2210c9..c233d8a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,2 @@ -language: dart \ No newline at end of file +language: dart +with_content_shell: true \ No newline at end of file diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 1614b397..3372d35f 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -111,7 +111,7 @@ abstract class BaseAngelClient extends Angel { if (json is! Map || !json.containsKey('data') || !json.containsKey('token')) { - throw new AngelHttpException.NotAuthenticated( + throw new AngelHttpException.notAuthenticated( message: "Auth endpoint '$url' did not return a proper response."); } @@ -127,6 +127,34 @@ abstract class BaseAngelClient extends Angel { client.close(); } + /// Sends a non-streaming [Request] and returns a non-streaming [Response]. + Future sendUnstreamed( + String method, url, Map headers, + [body, Encoding encoding]) async { + if (url is String) url = Uri.parse(url); + var request = new http.Request(method, url); + + if (headers != null) request.headers.addAll(headers); + + if (authToken?.isNotEmpty == true) + request.headers['Authorization'] = 'Bearer $authToken'; + + if (encoding != null) request.encoding = encoding; + if (body != null) { + if (body is String) { + request.body = body; + } else if (body is List) { + request.bodyBytes = DelegatingList.typed(body); + } else if (body is Map) { + request.bodyFields = DelegatingMap.typed(body); + } else { + throw new ArgumentError('Invalid request body "$body".'); + } + } + + return http.Response.fromStream(await client.send(request)); + } + @override Service service(String path, {Type type, AngelDeserializer deserializer}) { String uri = path.toString().replaceAll(straySlashes, ""); @@ -143,41 +171,41 @@ abstract class BaseAngelClient extends Angel { @override Future delete(String url, {Map headers}) async { - return client.delete(_join(url), headers: headers); + return sendUnstreamed('DELETE', _join(url), headers); } @override Future get(String url, {Map headers}) async { - return client.get(_join(url), headers: headers); + return sendUnstreamed('GET', _join(url), headers); } @override Future head(String url, {Map headers}) async { - return client.head(_join(url), headers: headers); + return sendUnstreamed('HEAD', _join(url), headers); } @override Future patch(String url, {body, Map headers}) async { - return client.patch(_join(url), body: body, headers: headers); + return sendUnstreamed('PATCH', _join(url), headers, body); } @override Future post(String url, {body, Map headers}) async { - return client.post(_join(url), body: body, headers: headers); + return sendUnstreamed('POST', _join(url), headers, body); } @override Future put(String url, {body, Map headers}) async { - return client.put(_join(url), body: body, headers: headers); + return sendUnstreamed('PUT', _join(url), headers, body); } } class BaseAngelService extends Service { @override - final Angel app; + final BaseAngelClient app; final String basePath; final http.BaseClient client; final AngelDeserializer deserializer; @@ -192,34 +220,6 @@ class BaseAngelService extends Service { return JSON.encode(x); } - /// Sends a non-streaming [Request] and returns a non-streaming [Response]. - Future sendUnstreamed( - String method, url, Map headers, - [body, Encoding encoding]) async { - if (url is String) url = Uri.parse(url); - var request = new http.Request(method, url); - - if (headers != null) request.headers.addAll(headers); - - if (app.authToken?.isNotEmpty == true) - request.headers['Authorization'] = 'Bearer ${app.authToken}'; - - if (encoding != null) request.encoding = encoding; - if (body != null) { - if (body is String) { - request.body = body; - } else if (body is List) { - request.bodyBytes = DelegatingList.typed(body); - } else if (body is Map) { - request.bodyFields = DelegatingMap.typed(body); - } else { - throw new ArgumentError('Invalid request body "$body".'); - } - } - - return http.Response.fromStream(await client.send(request)); - } - Future send(http.BaseRequest request) { if (app.authToken != null && app.authToken.isNotEmpty) { request.headers['Authorization'] = 'Bearer ${app.authToken}'; @@ -230,7 +230,7 @@ class BaseAngelService extends Service { @override Future index([Map params]) async { - final response = await sendUnstreamed( + final response = await app.sendUnstreamed( 'GET', '$basePath/${_buildQuery(params)}', _readHeaders); try { @@ -252,7 +252,7 @@ class BaseAngelService extends Service { @override Future read(id, [Map params]) async { - final response = await sendUnstreamed( + final response = await app.sendUnstreamed( 'GET', '$basePath/$id${_buildQuery(params)}', _readHeaders); try { @@ -268,7 +268,7 @@ class BaseAngelService extends Service { @override Future create(data, [Map params]) async { - final response = await sendUnstreamed('POST', + final response = await app.sendUnstreamed('POST', '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { @@ -284,7 +284,7 @@ class BaseAngelService extends Service { @override Future modify(id, data, [Map params]) async { - final response = await sendUnstreamed('PATCH', + final response = await app.sendUnstreamed('PATCH', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { @@ -300,7 +300,7 @@ class BaseAngelService extends Service { @override Future update(id, data, [Map params]) async { - final response = await sendUnstreamed('POST', + final response = await app.sendUnstreamed('POST', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { @@ -316,7 +316,7 @@ class BaseAngelService extends Service { @override Future remove(id, [Map params]) async { - final response = await sendUnstreamed( + final response = await app.sendUnstreamed( 'DELETE', '$basePath/$id${_buildQuery(params)}', _readHeaders); try { diff --git a/lib/io.dart b/lib/io.dart index 24841479..9b666f4c 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -29,7 +29,9 @@ class RestService extends BaseAngelService { deserialize(x) { if (type != null) { - return god.deserializeDatum(x, outputType: type); + return x.runtimeType == type + ? x + : god.deserializeDatum(x, outputType: type); } return x; diff --git a/test/io_test.dart b/test/io_test.dart index 158af35e..0ec00888 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -16,7 +16,8 @@ main() { String url; setUp(() async { - httpServer = await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + httpServer = + await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 0); url = "http://localhost:${httpServer.port}"; serverApp.use("/postcards", new server.MemoryService()); serverPostcards = serverApp.service("postcards"); @@ -39,7 +40,8 @@ main() { Postcard niagaraFalls = await serverPostcards.create( new Postcard(location: "Niagara Falls", message: "Missing you!")); print('Niagra Falls: ${niagaraFalls.toJson()}'); - List indexed = await clientPostcards.index(); + + List indexed = await clientPostcards.index(); print(indexed); expect(indexed.length, equals(1)); @@ -54,7 +56,9 @@ main() { List typedIndexed = await clientTypedPostcards.index(); expect(typedIndexed.length, equals(2)); expect(typedIndexed[1], equals(louvre)); - }); + }, + skip: + 'Index tests fails for some unknown reason, although it works in production.'); test("create/read", () async { Map opry = {"location": "Grand Ole Opry", "message": "Yeehaw!"}; @@ -71,7 +75,8 @@ main() { expect(read['location'], equals(created['location'])); expect(read['message'], equals(created['message'])); - Postcard canyon = new Postcard(location: "Grand Canyon", + Postcard canyon = new Postcard( + location: "Grand Canyon", message: "But did you REALLY experience it???"); created = await clientTypedPostcards.create(canyon); print(god.serialize(created)); @@ -89,8 +94,8 @@ main() { test("modify/update", () async { server.MemoryService innerPostcards = serverPostcards.inner; print(innerPostcards.items); - Postcard mecca = await clientTypedPostcards.create( - new Postcard(location: "Mecca", message: "Pilgrimage")); + Postcard mecca = await clientTypedPostcards + .create(new Postcard(location: "Mecca", message: "Pilgrimage")); print(god.serialize(mecca)); // I'm too lazy to write the tests twice, because I know it works @@ -102,15 +107,15 @@ main() { print("Postcards on client: " + god.serialize(await clientPostcards.index())); - Postcard modified = await clientTypedPostcards.modify( - mecca.id, {"location": "Saudi Arabia"}); + Postcard modified = await clientTypedPostcards + .modify(mecca.id, {"location": "Saudi Arabia"}); print(god.serialize(modified)); expect(modified.id, equals(mecca.id)); expect(modified.location, equals("Saudi Arabia")); expect(modified.message, equals(mecca.message)); - Map updated = await clientPostcards.update( - mecca.id, {"location": "Full", "message": "Overwrite"}); + Map updated = await clientPostcards + .update(mecca.id, {"location": "Full", "message": "Overwrite"}); print(updated); expect(updated.keys.length, equals(3)); @@ -120,10 +125,10 @@ main() { }); test("remove", () async { - Postcard remove1 = await clientTypedPostcards.create( - {"location": "remove", "message": "#1"}); - Postcard remove2 = await clientTypedPostcards.create( - {"location": "remove", "message": "#2"}); + Postcard remove1 = await clientTypedPostcards + .create({"location": "remove", "message": "#1"}); + Postcard remove2 = await clientTypedPostcards + .create({"location": "remove", "message": "#2"}); print(god.serialize([remove1, remove2])); Map removed1 = await clientPostcards.remove(remove1.id); From 05c79d1660e212e5084a89917f3093f614a98e3d Mon Sep 17 00:00:00 2001 From: thosakwe Date: Mon, 30 Jan 2017 23:00:58 -0500 Subject: [PATCH 27/69] 22 --- README.md | 2 +- pubspec.yaml | 2 +- test/io_test.dart | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4c1a36ec..f4a2f22d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0-dev+21](https://img.shields.io/badge/pub-1.0.0--dev+21-red.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.0-dev+22](https://img.shields.io/badge/pub-1.0.0--dev+22-red.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. diff --git a/pubspec.yaml b/pubspec.yaml index 8669f398..23f2c06e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+21 +version: 1.0.0-dev+22 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/io_test.dart b/test/io_test.dart index 0ec00888..b379ae4e 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -39,7 +39,7 @@ main() { test("index", () async { Postcard niagaraFalls = await serverPostcards.create( new Postcard(location: "Niagara Falls", message: "Missing you!")); - print('Niagra Falls: ${niagaraFalls.toJson()}'); + print('Niagara Falls: ${niagaraFalls.toJson()}'); List indexed = await clientPostcards.index(); print(indexed); @@ -53,7 +53,7 @@ main() { Postcard louvre = await serverPostcards.create(new Postcard( location: "The Louvre", message: "The Mona Lisa was watching me!")); print(god.serialize(louvre)); - List typedIndexed = await clientTypedPostcards.index(); + List typedIndexed = await clientTypedPostcards.index(); expect(typedIndexed.length, equals(2)); expect(typedIndexed[1], equals(louvre)); }, @@ -92,7 +92,8 @@ main() { }); test("modify/update", () async { - server.MemoryService innerPostcards = serverPostcards.inner; + var innerPostcards = + serverPostcards.inner as server.MemoryService; print(innerPostcards.items); Postcard mecca = await clientTypedPostcards .create(new Postcard(location: "Mecca", message: "Pilgrimage")); From f73b802392de196643f0c0b55b905481b0e33322 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 12 Feb 2017 14:58:18 -0500 Subject: [PATCH 28/69] 23 --- README.md | 2 +- lib/angel_client.dart | 2 +- lib/base_angel_client.dart | 8 ++------ lib/io.dart | 3 ++- pubspec.yaml | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f4a2f22d..2a9624ee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0-dev+22](https://img.shields.io/badge/pub-1.0.0--dev+22-red.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.0-dev+23](https://img.shields.io/badge/pub-1.0.0--dev+23-red.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 0915bdb8..86a808e1 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -90,7 +90,7 @@ abstract class Service { Angel get app; /// Retrieves all resources. - Future index([Map params]); + Future index([Map params]); /// Retrieves the desired resource. Future read(id, [Map params]); diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 3372d35f..831177ea 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -229,7 +229,7 @@ class BaseAngelService extends Service { } @override - Future index([Map params]) async { + Future index([Map params]) async { final response = await app.sendUnstreamed( 'GET', '$basePath/${_buildQuery(params)}', _readHeaders); @@ -239,11 +239,7 @@ class BaseAngelService extends Service { } final json = JSON.decode(response.body); - - if (json is! List) { - throw failure(response); - } - + if (json is! List) return json; return json.map(deserialize).toList(); } catch (e, st) { throw failure(response, error: e, stack: st); diff --git a/lib/io.dart b/lib/io.dart index 9b666f4c..caec159e 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -47,8 +47,9 @@ class RestService extends BaseAngelService { } @override - Future index([Map params]) async { + Future index([Map params]) async { final items = await super.index(params); + if (items is! List) return items; return items.map(deserialize).toList(); } diff --git a/pubspec.yaml b/pubspec.yaml index 23f2c06e..52d5a01a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+22 +version: 1.0.0-dev+23 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 38eb394078f87c48c1eb90ee93be0467a5cbec18 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 22 Feb 2017 17:20:30 -0500 Subject: [PATCH 29/69] 1.0.0 --- .travis.yml | 3 +-- README.md | 9 ++++----- lib/base_angel_client.dart | 6 +++--- pubspec.yaml | 2 +- test/io_test.dart | 11 ++++++----- test/shared.dart | 11 ++++++++--- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index c233d8a0..de2210c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1 @@ -language: dart -with_content_shell: true \ No newline at end of file +language: dart \ No newline at end of file diff --git a/README.md b/README.md index 2a9624ee..711e159a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ # angel_client -[![pub 1.0.0-dev+23](https://img.shields.io/badge/pub-1.0.0--dev+23-red.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.0](https://img.shields.io/badge/pub-1.0.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) ![build status](https://travis-ci.org/angel-dart/client.svg) Client library for the Angel framework. - -# Isomorphic -The REST client can run in the browser or on the command-line. +This library provides virtually the same API as an Angel server. +The client can run in the browser or on the command-line. +In addition, the client supports `angel_auth` authentication. # Usage -This library provides the same API as an Angel server. ```dart // Choose one or the other, depending on platform diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 831177ea..728df035 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -18,11 +18,11 @@ const Map _writeHeaders = const { }; _buildQuery(Map params) { - if (params == null || params.isEmpty) return ""; + if (params == null || params.isEmpty || params['query'] is! Map) return ""; List query = []; - params.forEach((k, v) { + params['query'].forEach((k, v) { query.add('$k=${Uri.encodeQueryComponent(v.toString())}'); }); @@ -231,7 +231,7 @@ class BaseAngelService extends Service { @override Future index([Map params]) async { final response = await app.sendUnstreamed( - 'GET', '$basePath/${_buildQuery(params)}', _readHeaders); + 'GET', '$basePath${_buildQuery(params)}', _readHeaders); try { if (response.statusCode != 200) { diff --git a/pubspec.yaml b/pubspec.yaml index 52d5a01a..e7f88483 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0-dev+23 +version: 1.0.0 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/io_test.dart b/test/io_test.dart index b379ae4e..fc7056a7 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -37,8 +37,10 @@ main() { }); test("index", () async { - Postcard niagaraFalls = await serverPostcards.create( + Map niagara = await clientPostcards.create( new Postcard(location: "Niagara Falls", message: "Missing you!")); + Postcard niagaraFalls = new Postcard.fromJson(niagara); + print('Niagara Falls: ${niagaraFalls.toJson()}'); List indexed = await clientPostcards.index(); @@ -50,15 +52,14 @@ main() { expect(indexed[0]['location'], equals(niagaraFalls.location)); expect(indexed[0]['message'], equals(niagaraFalls.message)); - Postcard louvre = await serverPostcards.create(new Postcard( + Map l = await clientPostcards.create(new Postcard( location: "The Louvre", message: "The Mona Lisa was watching me!")); + Postcard louvre = new Postcard.fromJson(l); print(god.serialize(louvre)); List typedIndexed = await clientTypedPostcards.index(); expect(typedIndexed.length, equals(2)); expect(typedIndexed[1], equals(louvre)); - }, - skip: - 'Index tests fails for some unknown reason, although it works in production.'); + }); test("create/read", () async { Map opry = {"location": "Grand Ole Opry", "message": "Yeehaw!"}; diff --git a/test/shared.dart b/test/shared.dart index 59031362..6d139b4e 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -1,10 +1,15 @@ -import "package:angel_framework/src/defs.dart"; +import "package:angel_framework/common.dart"; -class Postcard extends MemoryModel { +class Postcard extends Model { String location; String message; - Postcard({String this.location, String this.message}); + Postcard({String id, this.location, this.message}) { + this.id = id; + } + + factory Postcard.fromJson(Map data) => new Postcard( + id: data['id'], location: data['location'], message: data['message']); @override bool operator ==(other) { From 3e52ed62913b438c616bfe8f710163c8f14be1ff Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 22 Feb 2017 17:20:53 -0500 Subject: [PATCH 30/69] README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 711e159a..9a8d60a1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # angel_client [![pub 1.0.0](https://img.shields.io/badge/pub-1.0.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) -![build status](https://travis-ci.org/angel-dart/client.svg) +[![build status](https://travis-ci.org/angel-dart/client.svg)](https://travis-ci.org/angel-dart/client) Client library for the Angel framework. This library provides virtually the same API as an Angel server. From bea2aa03f98d17f2a814852d2e0026a6e87fa2cd Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 28 Feb 2017 16:56:59 -0500 Subject: [PATCH 31/69] 1.0.1 --- README.md | 2 +- lib/angel_client.dart | 17 ++++++++--------- lib/base_angel_client.dart | 3 --- lib/browser.dart | 29 +++++++++++++++++++++++++++-- lib/io.dart | 4 ++++ pubspec.yaml | 2 +- 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9a8d60a1..5b097e3a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.0](https://img.shields.io/badge/pub-1.0.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.1](https://img.shields.io/badge/pub-1.0.1-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) [![build status](https://travis-ci.org/angel-dart/client.svg)](https://travis-ci.org/angel-dart/client) Client library for the Angel framework. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 86a808e1..00acef77 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -17,7 +17,7 @@ typedef AngelDeserializer(x); /// Represents an Angel server that we are querying. abstract class Angel { - String get authToken; + String authToken; String basePath; Angel(String this.basePath); @@ -28,6 +28,9 @@ abstract class Angel { String authEndpoint: '/auth', String reviveEndpoint: '/auth/token'}); + /// Opens the [url] in a new window, and returns a [Stream] that will fire a JWT on successful authentication. + Stream authenticateViaPopup(String url, {String eventName: 'token'}); + Future close(); /// Applies an [AngelConfigurer] to this instance. @@ -37,21 +40,17 @@ abstract class Angel { Service service(String path, {Type type, AngelDeserializer deserializer}); - Future delete(String url, - {Map headers}); + Future delete(String url, {Map headers}); Future get(String url, {Map headers}); Future head(String url, {Map headers}); - Future patch(String url, - {body, Map headers}); + Future patch(String url, {body, Map headers}); - Future post(String url, - {body, Map headers}); + Future post(String url, {body, Map headers}); - Future put(String url, - {body, Map headers}); + Future put(String url, {body, Map headers}); } /// Represents the result of authentication with an Angel server. diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 728df035..639f6d2c 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -50,9 +50,6 @@ AngelHttpException failure(http.Response response, {error, StackTrace stack}) { } abstract class BaseAngelClient extends Angel { - @override - String authToken; - final http.BaseClient client; BaseAngelClient(this.client, String basePath) : super(basePath); diff --git a/lib/browser.dart b/lib/browser.dart index e112f20c..231c2340 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -1,9 +1,9 @@ /// Browser library for the Angel framework. library angel_client.browser; -import 'dart:async' show Future; +import 'dart:async' show Future, Stream, StreamController; import 'dart:convert' show JSON; -import 'dart:html' show window; +import 'dart:html' show CustomEvent, window; import 'package:http/browser_client.dart' as http; import 'angel_client.dart'; import 'auth_types.dart' as auth_types; @@ -45,4 +45,29 @@ class Rest extends BaseAngelClient { return result; } } + + @override + Stream authenticateViaPopup(String url, + {String eventName: 'token', String errorMessage}) { + var ctrl = new StreamController(); + var wnd = window.open(url, 'angel_client_auth_popup'); + + wnd + ..on['beforeunload'].listen((_) { + if (!ctrl.isClosed) { + ctrl.addError(new AngelHttpException.notAuthenticated( + message: + errorMessage ?? 'Authentication via popup window failed.')); + ctrl.close(); + } + }) + ..on[eventName ?? 'token'].listen((CustomEvent e) { + if (!ctrl.isClosed) { + ctrl.add(e.detail); + ctrl.close(); + } + }); + + return ctrl.stream; + } } diff --git a/lib/io.dart b/lib/io.dart index caec159e..b1ec9f1f 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -18,6 +18,10 @@ class Rest extends BaseAngelClient { return new RestService( client, this, "$basePath/$uri", T != dynamic ? T : type); } + @override + Stream authenticateViaPopup(String url, {String eventName: 'token'}) { + throw new UnimplementedError('Opening popup windows is not supported in the `dart:io` client.'); + } } /// Queries an Angel service via REST. diff --git a/pubspec.yaml b/pubspec.yaml index e7f88483..2fab6c13 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.0 +version: 1.0.1 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 41a42c7efe2cbbd13b91b009d4c967679b6fa501 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 3 Mar 2017 21:18:53 -0500 Subject: [PATCH 32/69] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b097e3a..6b132eb3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ import 'package:angel_client/cli.dart'; import 'package:angel_client/browser.dart'; main() async { - Angel app = new Rest("http://localhost:3000", new BrowserClient()); + Angel app = new Rest("http://localhost:3000"); } ``` From 49ec3f4e03b3e286d7e848594976142faff3eddf Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 3 Mar 2017 21:19:10 -0500 Subject: [PATCH 33/69] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b132eb3..a9f85eb8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ In addition, the client supports `angel_auth` authentication. ```dart // Choose one or the other, depending on platform -import 'package:angel_client/cli.dart'; +import 'package:angel_client/io.dart'; import 'package:angel_client/browser.dart'; main() async { From 8bb94fdc08d4a5a3506c42147b76db25ab0393c6 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 7 Mar 2017 16:54:13 -0500 Subject: [PATCH 34/69] 1.0.2 --- README.md | 2 +- lib/auth_types.dart | 1 - lib/base_angel_client.dart | 3 +-- lib/browser.dart | 3 ++- pubspec.yaml | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a9f85eb8..928be770 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.1](https://img.shields.io/badge/pub-1.0.1-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.2](https://img.shields.io/badge/pub-1.0.2-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) [![build status](https://travis-ci.org/angel-dart/client.svg)](https://travis-ci.org/angel-dart/client) Client library for the Angel framework. diff --git a/lib/auth_types.dart b/lib/auth_types.dart index 4328fa16..6c57d352 100644 --- a/lib/auth_types.dart +++ b/lib/auth_types.dart @@ -1,2 +1 @@ -const String GOOGLE = 'google'; const String LOCAL = 'local'; \ No newline at end of file diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 639f6d2c..73e56a49 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -8,7 +8,6 @@ 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 'angel_client.dart'; -import 'auth_types.dart' as auth_types; final RegExp straySlashes = new RegExp(r"(^/)|(/+$)"); const Map _readHeaders = const {'Accept': 'application/json'}; @@ -56,7 +55,7 @@ abstract class BaseAngelClient extends Angel { @override Future authenticate( - {String type: auth_types.LOCAL, + {String type, credentials, String authEndpoint: '/auth', String reviveEndpoint: '/auth/token'}) async { diff --git a/lib/browser.dart b/lib/browser.dart index 231c2340..1bfe09a2 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -16,7 +16,7 @@ class Rest extends BaseAngelClient { @override Future authenticate( - {String type: auth_types.LOCAL, + {String type, credentials, String authEndpoint: '/auth', String reviveEndpoint: '/auth/token'}) async { @@ -28,6 +28,7 @@ class Rest extends BaseAngelClient { try { final result = await super.authenticate( + type: null, credentials: {'token': JSON.decode(window.localStorage['token'])}, reviveEndpoint: reviveEndpoint); window.localStorage['token'] = JSON.encode(authToken = result.token); diff --git a/pubspec.yaml b/pubspec.yaml index 2fab6c13..67bd32be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.1 +version: 1.0.2 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 3564fb4e359a9f973c71f7f1ab2c3f70f43eaed0 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Fri, 17 Mar 2017 15:21:09 -0400 Subject: [PATCH 35/69] 1.0.3 --- README.md | 2 +- lib/browser.dart | 25 ++++++++++++++----------- pubspec.yaml | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 928be770..cfedff33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.2](https://img.shields.io/badge/pub-1.0.2-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.3](https://img.shields.io/badge/pub-1.0.3-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) [![build status](https://travis-ci.org/angel-dart/client.svg)](https://travis-ci.org/angel-dart/client) Client library for the Angel framework. diff --git a/lib/browser.dart b/lib/browser.dart index 1bfe09a2..a506c624 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -1,7 +1,7 @@ /// Browser library for the Angel framework. library angel_client.browser; -import 'dart:async' show Future, Stream, StreamController; +import 'dart:async' show Future, Stream, StreamController, Timer; import 'dart:convert' show JSON; import 'dart:html' show CustomEvent, window; import 'package:http/browser_client.dart' as http; @@ -53,21 +53,24 @@ class Rest extends BaseAngelClient { var ctrl = new StreamController(); var wnd = window.open(url, 'angel_client_auth_popup'); - wnd - ..on['beforeunload'].listen((_) { - if (!ctrl.isClosed) { + new Timer.periodic(new Duration(milliseconds: 500), (timer) { + if (!ctrl.isClosed) { + if (wnd.closed) { ctrl.addError(new AngelHttpException.notAuthenticated( message: errorMessage ?? 'Authentication via popup window failed.')); ctrl.close(); } - }) - ..on[eventName ?? 'token'].listen((CustomEvent e) { - if (!ctrl.isClosed) { - ctrl.add(e.detail); - ctrl.close(); - } - }); + } else + timer.cancel(); + }); + + window.on[eventName ?? 'token'].listen((CustomEvent e) { + if (!ctrl.isClosed) { + ctrl.add(e.detail); + ctrl.close(); + } + }); return ctrl.stream; } diff --git a/pubspec.yaml b/pubspec.yaml index 67bd32be..7c910e01 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.2 +version: 1.0.3 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From f1ce32d07ee983f8b53630a10b56a77687202290 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 28 Mar 2017 21:52:19 -0400 Subject: [PATCH 36/69] 1.0.4 --- README.md | 36 +++++++++++++++++++++++++++++++++++- dart_test.yaml | 1 + lib/angel_client.dart | 3 +++ lib/base_angel_client.dart | 25 +++++++++++++++++-------- lib/browser.dart | 13 +++++++++++-- pubspec.yaml | 2 +- test/browser_test.dart | 6 ++---- test/for_browser_tests.dart | 8 ++++---- 8 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 dart_test.yaml diff --git a/README.md b/README.md index cfedff33..d6061ae3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.3](https://img.shields.io/badge/pub-1.0.3-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.4](https://img.shields.io/badge/pub-1.0.4-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) [![build status](https://travis-ci.org/angel-dart/client.svg)](https://travis-ci.org/angel-dart/client) Client library for the Angel framework. @@ -54,3 +54,37 @@ bar() async { 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); +print(auth.user); +``` + +Revive an existing jwt: +```dart +Future reviveJwt(String jwt) { + return app.authenticate(credentials: {'token': jwt}); +} +``` + +Via Popup: +```dart +app.authenticateViaPopup('/auth/google').listen((jwt) { + // Do something with the JWT +}); +``` + +Resume a session from localStorage (browser only): +```dart +// Automatically checks for JSON-encoded 'token' in localStorage, +// and tries to revive it +await app.authenticate(); +``` + +Logout: +```dart +await app.logout(); +``` \ No newline at end of file diff --git a/dart_test.yaml b/dart_test.yaml new file mode 100644 index 00000000..c81b6f17 --- /dev/null +++ b/dart_test.yaml @@ -0,0 +1 @@ +# platforms: [vm, content-shell] \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 00acef77..2061f125 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -38,6 +38,9 @@ abstract class Angel { await configurer(this); } + /// Logs the current user out of the application. + Future logout(); + Service service(String path, {Type type, AngelDeserializer deserializer}); Future delete(String url, {Map headers}); diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 73e56a49..7fadd73a 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -28,6 +28,11 @@ _buildQuery(Map params) { return '?' + query.join('&'); } +bool _invalid(http.Response response) => + response.statusCode == null || + response.statusCode < 200 || + response.statusCode >= 300; + AngelHttpException failure(http.Response response, {error, StackTrace stack}) { try { final json = JSON.decode(response.body); @@ -68,7 +73,7 @@ abstract class BaseAngelClient extends Angel { }); try { - if (response.statusCode != 200) { + if (_invalid(response)) { throw failure(response); } @@ -98,7 +103,7 @@ abstract class BaseAngelClient extends Angel { } try { - if (response.statusCode != 200) { + if (_invalid(response)) { throw failure(response); } @@ -123,6 +128,10 @@ abstract class BaseAngelClient extends Angel { client.close(); } + Future logout() async { + authToken = null; + } + /// Sends a non-streaming [Request] and returns a non-streaming [Response]. Future sendUnstreamed( String method, url, Map headers, @@ -230,7 +239,7 @@ class BaseAngelService extends Service { 'GET', '$basePath${_buildQuery(params)}', _readHeaders); try { - if (response.statusCode != 200) { + if (_invalid(response)) { throw failure(response); } @@ -248,7 +257,7 @@ class BaseAngelService extends Service { 'GET', '$basePath/$id${_buildQuery(params)}', _readHeaders); try { - if (response.statusCode != 200) { + if (_invalid(response)) { throw failure(response); } @@ -264,7 +273,7 @@ class BaseAngelService extends Service { '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { - if (response.statusCode != 200) { + if (_invalid(response)) { throw failure(response); } @@ -280,7 +289,7 @@ class BaseAngelService extends Service { '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { - if (response.statusCode != 200) { + if (_invalid(response)) { throw failure(response); } @@ -296,7 +305,7 @@ class BaseAngelService extends Service { '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); try { - if (response.statusCode != 200) { + if (_invalid(response)) { throw failure(response); } @@ -312,7 +321,7 @@ class BaseAngelService extends Service { 'DELETE', '$basePath/$id${_buildQuery(params)}', _readHeaders); try { - if (response.statusCode != 200) { + if (_invalid(response)) { throw failure(response); } diff --git a/lib/browser.dart b/lib/browser.dart index a506c624..a47150c7 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -6,7 +6,7 @@ import 'dart:convert' show JSON; import 'dart:html' show CustomEvent, window; import 'package:http/browser_client.dart' as http; import 'angel_client.dart'; -import 'auth_types.dart' as auth_types; +// import 'auth_types.dart' as auth_types; import 'base_angel_client.dart'; export 'angel_client.dart'; @@ -53,13 +53,15 @@ class Rest extends BaseAngelClient { var ctrl = new StreamController(); var wnd = window.open(url, 'angel_client_auth_popup'); - new Timer.periodic(new Duration(milliseconds: 500), (timer) { + Timer t; + t = new Timer.periodic(new Duration(milliseconds: 500), (timer) { if (!ctrl.isClosed) { if (wnd.closed) { ctrl.addError(new AngelHttpException.notAuthenticated( message: errorMessage ?? 'Authentication via popup window failed.')); ctrl.close(); + timer.cancel(); } } else timer.cancel(); @@ -68,10 +70,17 @@ class Rest extends BaseAngelClient { window.on[eventName ?? 'token'].listen((CustomEvent e) { if (!ctrl.isClosed) { ctrl.add(e.detail); + t.cancel(); ctrl.close(); } }); return ctrl.stream; } + + @override + Future logout() { + window.localStorage.remove('token'); + return super.logout(); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 7c910e01..963444d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.3 +version: 1.0.4 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/browser_test.dart b/test/browser_test.dart index 8eb28db9..c6b71bac 100644 --- a/test/browser_test.dart +++ b/test/browser_test.dart @@ -6,8 +6,7 @@ import 'for_browser_tests.dart'; main() { test("list todos", () async { var channel = spawnHybridCode(SERVER); - int port = await channel.stream.first; - var url = "http://localhost:$port"; + String url = await channel.stream.first; print(url); var app = new Rest(url); var todoService = app.service("todos"); @@ -18,8 +17,7 @@ main() { test('create todos', () async { var channel = spawnHybridCode(SERVER); - int port = await channel.stream.first; - var url = "http://localhost:$port"; + String url = await channel.stream.first; print(url); var app = new Rest(url); var todoService = app.service("todos"); diff --git a/test/for_browser_tests.dart b/test/for_browser_tests.dart index e23cb79a..12378097 100644 --- a/test/for_browser_tests.dart +++ b/test/for_browser_tests.dart @@ -1,7 +1,7 @@ const String SERVER = ''' import 'dart:io'; import "package:angel_framework/angel_framework.dart"; -import "package:angel_framework/src/defs.dart"; +import "package:angel_framework/common.dart"; import 'package:stream_channel/stream_channel.dart'; hybridMain(StreamChannel channel) async { @@ -12,15 +12,15 @@ hybridMain(StreamChannel channel) async { return true; }); - app.use("/todos", new MemoryService()); + app.use("/todos", new TypedService(new MapService())); var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); print("Server up; listening at http://localhost:\${server.port}"); - channel.sink.add(server.port); + channel.sink.add('http://\${server.address.address}:\${server.port}'); } -class Todo extends MemoryModel { +class Todo extends Model { String hello; Todo({int id, this.hello}) { From 2479e8eaa77c80a929cdb170a91b30bafab140c6 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 30 Mar 2017 15:00:02 -0400 Subject: [PATCH 37/69] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d6061ae3..07f70ac6 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Local auth: ```dart var auth = await app.authenticate(type: 'local', credentials: {username: ..., password: ...}); print(auth.token); -print(auth.user); +print(auth.data); // User object ``` Revive an existing jwt: @@ -87,4 +87,4 @@ await app.authenticate(); Logout: ```dart await app.logout(); -``` \ No newline at end of file +``` From f2fcc48642ff332870e9184235d326ce732b9bf9 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Jun 2017 13:43:01 -0400 Subject: [PATCH 38/69] 1.0.5 --- README.md | 2 +- lib/angel_client.dart | 27 ++++++++++- lib/base_angel_client.dart | 93 +++++++++++++++++++++++++++++++++----- lib/io.dart | 44 +++++++----------- pubspec.yaml | 2 +- test/io_test.dart | 4 +- 6 files changed, 126 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 07f70ac6..4f5d527b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.4](https://img.shields.io/badge/pub-1.0.4-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.5](https://img.shields.io/badge/pub-1.0.5-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) [![build status](https://travis-ci.org/angel-dart/client.svg)](https://travis-ci.org/angel-dart/client) Client library for the Angel framework. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 2061f125..04ed80c8 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -22,6 +22,9 @@ abstract class Angel { Angel(String this.basePath); + /// Fired whenever a WebSocket is successfully authenticated. + Stream get onAuthenticated; + Future authenticate( {String type, credentials, @@ -41,7 +44,7 @@ abstract class Angel { /// Logs the current user out of the application. Future logout(); - Service service(String path, {Type type, AngelDeserializer deserializer}); + Service service(String path, {Type type, AngelDeserializer deserializer}); Future delete(String url, {Map headers}); @@ -87,10 +90,30 @@ class AngelAuthResult { } /// Queries a service on an Angel server, with the same API. -abstract class Service { +abstract class Service { + /// Fired on `indexed` events. + Stream get onIndexed; + + /// Fired on `read` events. + Stream get onRead; + + /// Fired on `created` events. + Stream get onCreated; + + /// Fired on `modified` events. + Stream get onModified; + + /// Fired on `updated` events. + Stream get onUpdated; + + /// Fired on `removed` events. + Stream get onRemoved; + /// The Angel instance powering this service. Angel get app; + Future close(); + /// Retrieves all resources. Future index([Map params]); diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 7fadd73a..e9552ff1 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -54,8 +54,14 @@ AngelHttpException failure(http.Response response, {error, StackTrace stack}) { } abstract class BaseAngelClient extends Angel { + final StreamController _onAuthenticated = + new StreamController(); + final List _services = []; final http.BaseClient client; + @override + Stream get onAuthenticated => _onAuthenticated.stream; + BaseAngelClient(this.client, String basePath) : super(basePath); @override @@ -87,7 +93,9 @@ abstract class BaseAngelClient extends Angel { "Auth endpoint '$url' did not return a proper response."); } - return new AngelAuthResult.fromMap(json); + var r = new AngelAuthResult.fromMap(json); + _onAuthenticated.add(r); + return r; } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -117,7 +125,9 @@ abstract class BaseAngelClient extends Angel { "Auth endpoint '$url' did not return a proper response."); } - return new AngelAuthResult.fromMap(json); + var r = new AngelAuthResult.fromMap(json); + _onAuthenticated.add(r); + return r; } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -126,6 +136,10 @@ abstract class BaseAngelClient extends Angel { Future close() async { client.close(); + _onAuthenticated.close(); + Future.wait(_services.map((s) => s.close())).then((_) { + _services.clear(); + }); } Future logout() async { @@ -161,10 +175,13 @@ abstract class BaseAngelClient extends Angel { } @override - Service service(String path, {Type type, AngelDeserializer deserializer}) { + Service service(String path, + {Type type, AngelDeserializer deserializer}) { String uri = path.toString().replaceAll(straySlashes, ""); - return new BaseAngelService(client, this, '$basePath/$uri', + var s = new BaseAngelService(client, this, '$basePath/$uri', deserializer: deserializer); + _services.add(s); + return s; } String _join(url) { @@ -208,13 +225,48 @@ abstract class BaseAngelClient extends Angel { } } -class BaseAngelService extends Service { +class BaseAngelService extends Service { @override final BaseAngelClient app; final String basePath; final http.BaseClient client; final AngelDeserializer deserializer; + final StreamController _onIndexed = new StreamController(), + _onRead = new StreamController(), + _onCreated = new StreamController(), + _onModified = new StreamController(), + _onUpdated = new StreamController(), + _onRemoved = new StreamController(); + + @override + Stream get onIndexed => _onIndexed.stream; + + @override + Stream get onRead => _onRead.stream; + + @override + Stream get onCreated => _onCreated.stream; + + @override + Stream get onModified => _onModified.stream; + + @override + Stream get onUpdated => _onUpdated.stream; + + @override + Stream get onRemoved => _onRemoved.stream; + + @override + Future close() async { + _onIndexed.close(); + _onRead.close(); + _onCreated.close(); + _onModified.close(); + _onUpdated.close(); + _onRemoved.close(); + } + BaseAngelService(this.client, this.app, this.basePath, {this.deserializer}); deserialize(x) { @@ -244,8 +296,15 @@ class BaseAngelService extends Service { } final json = JSON.decode(response.body); - if (json is! List) return json; - return json.map(deserialize).toList(); + + if (json is! List) { + _onIndexed.add(json); + return json; + } + + var r = json.map(deserialize).toList(); + _onIndexed.add(r); + return r; } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -261,7 +320,9 @@ class BaseAngelService extends Service { throw failure(response); } - return deserialize(JSON.decode(response.body)); + var r = deserialize(JSON.decode(response.body)); + _onRead.add(r); + return r; } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -277,7 +338,9 @@ class BaseAngelService extends Service { throw failure(response); } - return deserialize(JSON.decode(response.body)); + var r = deserialize(JSON.decode(response.body)); + _onCreated.add(r); + return r; } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -293,7 +356,9 @@ class BaseAngelService extends Service { throw failure(response); } - return deserialize(JSON.decode(response.body)); + var r = deserialize(JSON.decode(response.body)); + _onModified.add(r); + return r; } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -309,7 +374,9 @@ class BaseAngelService extends Service { throw failure(response); } - return deserialize(JSON.decode(response.body)); + var r = deserialize(JSON.decode(response.body)); + _onUpdated.add(r); + return r; } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -325,7 +392,9 @@ class BaseAngelService extends Service { throw failure(response); } - return deserialize(JSON.decode(response.body)); + var r = deserialize(JSON.decode(response.body)); + _onRemoved.add(r); + return r; } catch (e, st) { throw failure(response, error: e, stack: st); } diff --git a/lib/io.dart b/lib/io.dart index b1ec9f1f..1d450681 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -10,27 +10,40 @@ export 'angel_client.dart'; /// Queries an Angel server via REST. class Rest extends BaseAngelClient { + final List _services = []; + Rest(String path) : super(new http.Client(), path); @override - Service service(String path, {Type type, AngelDeserializer deserializer}) { + Service service(String path, {Type type, AngelDeserializer deserializer}) { String uri = path.replaceAll(straySlashes, ""); - return new RestService( + var s = new RestService( client, this, "$basePath/$uri", T != dynamic ? T : type); + _services.add(s); + return s; } + @override Stream authenticateViaPopup(String url, {String eventName: 'token'}) { throw new UnimplementedError('Opening popup windows is not supported in the `dart:io` client.'); } + + Future close() async { + super.close(); + Future.wait(_services.map((s) => s.close())).then((_) { + _services.clear(); + }); + } } /// Queries an Angel service via REST. -class RestService extends BaseAngelService { +class RestService extends BaseAngelService { final Type type; RestService(http.BaseClient client, Angel app, String url, this.type) : super(client, app, url); + @override deserialize(x) { if (type != null) { return x.runtimeType == type @@ -49,29 +62,4 @@ class RestService extends BaseAngelService { return super.makeBody(x); } - - @override - Future index([Map params]) async { - final items = await super.index(params); - if (items is! List) return items; - return items.map(deserialize).toList(); - } - - @override - Future read(id, [Map params]) => super.read(id, params).then(deserialize); - - @override - Future create(data, [Map params]) => - super.create(data, params).then(deserialize); - - @override - Future modify(id, data, [Map params]) => - super.modify(id, data, params).then(deserialize); - - @override - Future update(id, data, [Map params]) => - super.update(id, data, params).then(deserialize); - - @override - Future remove(id, [Map params]) => super.remove(id, params).then(deserialize); } diff --git a/pubspec.yaml b/pubspec.yaml index 963444d6..0ff7ee9d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.4 +version: 1.0.5 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/io_test.dart b/test/io_test.dart index fc7056a7..61c8882e 100644 --- a/test/io_test.dart +++ b/test/io_test.dart @@ -19,7 +19,7 @@ main() { httpServer = await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 0); url = "http://localhost:${httpServer.port}"; - serverApp.use("/postcards", new server.MemoryService()); + serverApp.use("/postcards", new server.TypedService(new server.MapService())); serverPostcards = serverApp.service("postcards"); clientApp = new client.Rest(url); @@ -94,7 +94,7 @@ main() { test("modify/update", () async { var innerPostcards = - serverPostcards.inner as server.MemoryService; + serverPostcards.inner as server.TypedService; print(innerPostcards.items); Postcard mecca = await clientTypedPostcards .create(new Postcard(location: "Mecca", message: "Pilgrimage")); From 25da3788f71467f4e1b5243c2a3758feef15c5c6 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Jun 2017 13:46:49 -0400 Subject: [PATCH 39/69] Cancel sub --- README.md | 2 +- lib/browser.dart | 7 +++++-- pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4f5d527b..085aeb78 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_client -[![pub 1.0.5](https://img.shields.io/badge/pub-1.0.5-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) +[![pub 1.0.6](https://img.shields.io/badge/pub-1.0.6-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) [![build status](https://travis-ci.org/angel-dart/client.svg)](https://travis-ci.org/angel-dart/client) Client library for the Angel framework. diff --git a/lib/browser.dart b/lib/browser.dart index a47150c7..59faf0f3 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -1,7 +1,7 @@ /// Browser library for the Angel framework. library angel_client.browser; -import 'dart:async' show Future, Stream, StreamController, Timer; +import 'dart:async' show Future, Stream, StreamController, StreamSubscription, Timer; import 'dart:convert' show JSON; import 'dart:html' show CustomEvent, window; import 'package:http/browser_client.dart' as http; @@ -54,6 +54,7 @@ class Rest extends BaseAngelClient { var wnd = window.open(url, 'angel_client_auth_popup'); Timer t; + StreamSubscription sub; t = new Timer.periodic(new Duration(milliseconds: 500), (timer) { if (!ctrl.isClosed) { if (wnd.closed) { @@ -62,16 +63,18 @@ class Rest extends BaseAngelClient { errorMessage ?? 'Authentication via popup window failed.')); ctrl.close(); timer.cancel(); + sub?.cancel(); } } else timer.cancel(); }); - window.on[eventName ?? 'token'].listen((CustomEvent e) { + sub = window.on[eventName ?? 'token'].listen((CustomEvent e) { if (!ctrl.isClosed) { ctrl.add(e.detail); t.cancel(); ctrl.close(); + sub.cancel(); } }); diff --git a/pubspec.yaml b/pubspec.yaml index 0ff7ee9d..22342d56 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.5 +version: 1.0.6 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From e08aff95078e428b492f6fcd7189cf9f90a9164a Mon Sep 17 00:00:00 2001 From: thosakwe Date: Fri, 30 Jun 2017 18:57:01 -0400 Subject: [PATCH 40/69] Added Flutter lib --- README.md | 4 ++-- lib/browser.dart | 7 ++++--- lib/flutter.dart | 17 +++++++++++++++++ pubspec.yaml | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 lib/flutter.dart diff --git a/README.md b/README.md index 085aeb78..8f08fd61 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # angel_client -[![pub 1.0.6](https://img.shields.io/badge/pub-1.0.6-brightgreen.svg)](https://pub.dartlang.org/packages/angel_client) +[![Pub](https://img.shields.io/pub/v/angel_client.svg)](https://pub.dartlang.org/packages/angel_client) [![build status](https://travis-ci.org/angel-dart/client.svg)](https://travis-ci.org/angel-dart/client) Client library for the Angel framework. This library provides virtually the same API as an Angel server. -The client can run in the browser or on the command-line. +The client can run in the browser, in Flutter, or on the command-line. In addition, the client supports `angel_auth` authentication. # Usage diff --git a/lib/browser.dart b/lib/browser.dart index 59faf0f3..996eb849 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -1,9 +1,9 @@ -/// Browser library for the Angel framework. +/// Browser client library for the Angel framework. library angel_client.browser; import 'dart:async' show Future, Stream, StreamController, StreamSubscription, Timer; import 'dart:convert' show JSON; -import 'dart:html' show CustomEvent, window; +import 'dart:html' show CustomEvent, Event, window; import 'package:http/browser_client.dart' as http; import 'angel_client.dart'; // import 'auth_types.dart' as auth_types; @@ -69,7 +69,8 @@ class Rest extends BaseAngelClient { timer.cancel(); }); - sub = window.on[eventName ?? 'token'].listen((CustomEvent e) { + sub = window.on[eventName ?? 'token'].listen((Event ev) { + var e = ev as CustomEvent; if (!ctrl.isClosed) { ctrl.add(e.detail); t.cancel(); diff --git a/lib/flutter.dart b/lib/flutter.dart new file mode 100644 index 00000000..3b407932 --- /dev/null +++ b/lib/flutter.dart @@ -0,0 +1,17 @@ +/// Flutter-compatible client library for the Angel framework. +library angel_client.flutter; + +import 'dart:async'; +import 'package:http/http.dart' as http; +import 'base_angel_client.dart'; +export 'angel_client.dart'; + +/// Queries an Angel server via REST. +class Rest extends BaseAngelClient { + Rest(String basePath) : super(new http.Client(), basePath); + + @override + Stream authenticateViaPopup(String url, {String eventName: 'token'}) { + throw new UnimplementedError('Opening popup windows is not supported in the `dart:io` client.'); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 22342d56..3eaaa991 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.0.6 +version: 1.0.7 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 6ff96bff493e733b2e3b363301a0caf737418b65 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 13 Jul 2017 13:52:42 -0400 Subject: [PATCH 41/69] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8f08fd61..3c8f1360 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ In addition, the client supports `angel_auth` authentication. // Choose one or the other, depending on platform import 'package:angel_client/io.dart'; import 'package:angel_client/browser.dart'; +import 'package:angel_client/flutter.dart'; main() async { Angel app = new Rest("http://localhost:3000"); From eabf105bd6e99d91133256e6aa4f4022295d4e35 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 24 Sep 2017 00:12:53 -0400 Subject: [PATCH 42/69] 1.1.0-alpha --- lib/angel_client.dart | 2 +- lib/base_angel_client.dart | 36 ++++++--- pubspec.yaml | 7 +- test/all_test.dart | 94 +++++++++++++++++++++++ test/browser_test.dart | 35 --------- test/common.dart | 67 +++++++++++++++++ test/for_browser_tests.dart | 30 -------- test/io_test.dart | 146 ------------------------------------ test/shared.dart | 2 +- 9 files changed, 194 insertions(+), 225 deletions(-) create mode 100644 test/all_test.dart delete mode 100644 test/browser_test.dart create mode 100644 test/common.dart delete mode 100644 test/for_browser_tests.dart delete mode 100644 test/io_test.dart diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 04ed80c8..e6bde541 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -4,7 +4,7 @@ library angel_client; import 'dart:async'; import 'dart:convert'; import 'package:http/src/response.dart' as http; -export 'package:angel_framework/src/http/angel_http_exception.dart'; +export 'package:angel_http_exception/angel_http_exception.dart'; /// A function that configures an [Angel] client in some way. typedef Future AngelConfigurer(Angel app); diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index e9552ff1..8f9884da 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:angel_framework/src/http/angel_http_exception.dart'; +import 'package:angel_http_exception/angel_http_exception.dart'; import 'package:collection/collection.dart'; import 'package:http/src/base_client.dart' as http; import 'package:http/src/base_request.dart' as http; @@ -72,17 +72,29 @@ abstract class BaseAngelClient extends Angel { String reviveEndpoint: '/auth/token'}) async { if (type == null) { final url = '$basePath$reviveEndpoint'; + String token; + + if (credentials is String) + token = credentials; + else if (credentials is Map && credentials.containsKey('token')) + token = credentials['token']; + + if (token == null) { + throw new ArgumentError( + 'If `type` is not set, a JWT is expected as the `credentials` argument.'); + } + final response = await client.post(url, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', - 'Authorization': 'Bearer ${credentials['token']}' + 'Authorization': 'Bearer $token' }); - try { - if (_invalid(response)) { - throw failure(response); - } + if (_invalid(response)) { + throw failure(response); + } + try { final json = JSON.decode(response.body); if (json is! Map || @@ -96,6 +108,8 @@ abstract class BaseAngelClient extends Angel { var r = new AngelAuthResult.fromMap(json); _onAuthenticated.add(r); return r; + } on AngelHttpException { + rethrow; } catch (e, st) { throw failure(response, error: e, stack: st); } @@ -110,11 +124,11 @@ abstract class BaseAngelClient extends Angel { response = await client.post(url, headers: _writeHeaders); } - try { - if (_invalid(response)) { - throw failure(response); - } + if (_invalid(response)) { + throw failure(response); + } + try { final json = JSON.decode(response.body); if (json is! Map || @@ -128,6 +142,8 @@ abstract class BaseAngelClient extends Angel { var r = new AngelAuthResult.fromMap(json); _onAuthenticated.add(r); return r; + } on AngelHttpException { + rethrow; } catch (e, st) { throw failure(response, error: e, stack: st); } diff --git a/pubspec.yaml b/pubspec.yaml index 3eaaa991..954111d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,17 @@ name: angel_client -version: 1.0.7 +version: 1.1.0-alpha description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client environment: sdk: ">=1.21.0" dependencies: - angel_framework: ">=1.0.0-dev <2.0.0" + angel_http_exception: ^1.0.0 http: ">= 0.11.3 < 0.12.0" json_god: ">=2.0.0-beta <3.0.0" merge_map: ">=1.0.0 <2.0.0" dev_dependencies: + angel_framework: ^1.1.0-alpha + angel_model: ^1.0.0 + mock_request: ^1.0.0 test: ">= 0.12.13 < 0.13.0" diff --git a/test/all_test.dart b/test/all_test.dart new file mode 100644 index 00000000..2bd2d10b --- /dev/null +++ b/test/all_test.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; +import 'package:angel_client/angel_client.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +main() { + var app = new MockAngel(); + Service todoService = app.service('api/todos'); + + test('sets method,body,headers,path', () async { + await app.post('/post', headers: {'method': 'post'}, body: 'post'); + expect(app.client.spec.method, 'POST'); + expect(app.client.spec.path, '/post'); + expect(app.client.spec.headers['method'], 'post'); + expect(await read(app.client.spec.request.finalize()), 'post'); + }); + + group('service methods', () { + test('index', () async { + await todoService.index(); + expect(app.client.spec.method, 'GET'); + expect(app.client.spec.path, '/api/todos'); + }); + + test('read', () async { + await todoService.read('sleep'); + expect(app.client.spec.method, 'GET'); + expect(app.client.spec.path, '/api/todos/sleep'); + }); + + test('create', () async { + await todoService.create({}); + expect(app.client.spec.method, 'POST'); + expect(app.client.spec.headers['content-type'], + startsWith('application/json')); + expect(app.client.spec.path, '/api/todos/'); + expect(await read(app.client.spec.request.finalize()), '{}'); + }); + + test('modify', () async { + await todoService.modify('sleep', {}); + expect(app.client.spec.method, 'PATCH'); + expect(app.client.spec.headers['content-type'], + startsWith('application/json')); + expect(app.client.spec.path, '/api/todos/sleep'); + expect(await read(app.client.spec.request.finalize()), '{}'); + }); + + test('update', () async { + await todoService.update('sleep', {}); + expect(app.client.spec.method, 'POST'); + expect(app.client.spec.headers['content-type'], + startsWith('application/json')); + expect(app.client.spec.path, '/api/todos/sleep'); + expect(await read(app.client.spec.request.finalize()), '{}'); + }); + + test('remove', () async { + await todoService.remove('sleep'); + expect(app.client.spec.method, 'DELETE'); + expect(app.client.spec.path, '/api/todos/sleep'); + }); + }); + + group('authentication', () { + test('no type, no token throws', () async { + expect(app.authenticate, throwsArgumentError); + }); + + test('no type defaults to token', () async { + await app.authenticate(credentials: ''); + expect(app.client.spec.path, '/auth/token'); + }); + + test('sets type', () async { + await app.authenticate(type: 'local'); + expect(app.client.spec.path, '/auth/local'); + }); + + test('token sends headers', () async { + await app.authenticate(credentials: ''); + expect(app.client.spec.headers['authorization'], 'Bearer '); + }); + + test('credentials send right body', () async { + await app + .authenticate(type: 'local', credentials: {'username': 'password'}); + expect( + await read(app.client.spec.request.finalize()), + JSON.encode({'username': 'password'}), + ); + }); + }); +} diff --git a/test/browser_test.dart b/test/browser_test.dart deleted file mode 100644 index c6b71bac..00000000 --- a/test/browser_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -@TestOn('browser') -import 'package:angel_client/browser.dart'; -import 'package:test/test.dart'; -import 'for_browser_tests.dart'; - -main() { - test("list todos", () async { - var channel = spawnHybridCode(SERVER); - String url = await channel.stream.first; - print(url); - var app = new Rest(url); - var todoService = app.service("todos"); - - var todos = await todoService.index(); - expect(todos, isEmpty); - }); - - test('create todos', () async { - var channel = spawnHybridCode(SERVER); - String url = await channel.stream.first; - print(url); - var app = new Rest(url); - var todoService = app.service("todos"); - - var data = {'hello': 'world'}; - var response = await todoService.create(data); - print('Created response: $response'); - - var todos = await todoService.index(); - expect(todos, hasLength(1)); - - Map todo = todos.first; - expect(todo, equals(data)); - }); -} diff --git a/test/common.dart b/test/common.dart new file mode 100644 index 00000000..536665a2 --- /dev/null +++ b/test/common.dart @@ -0,0 +1,67 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:angel_client/base_angel_client.dart'; +import 'package:http/src/base_client.dart' as http; +import 'package:http/src/base_request.dart' as http; +import 'package:http/src/streamed_response.dart' as http; + +Future read(Stream> stream) => + stream.transform(UTF8.decoder).join(); + +class MockAngel extends BaseAngelClient { + @override + final SpecClient client = new SpecClient(); + + MockAngel() : super(null, 'http://localhost:3000'); + + @override + authenticateViaPopup(String url, {String eventName: 'token'}) { + throw new UnsupportedError('Nope'); + } +} + +class SpecClient extends http.BaseClient { + Spec _spec; + + Spec get spec => _spec; + + @override + send(http.BaseRequest request) { + _spec = new Spec(request, request.method, request.url.path, request.headers, + request.contentLength); + var data = {'text': 'Clean your room!', 'completed': true}; + + if (request.url.path.contains('auth')) + data = { + 'token': '', + 'data': {'username': 'password'} + }; + + return new Future.value(new http.StreamedResponse( + new Stream>.fromIterable([UTF8.encode(JSON.encode(data))]), + 200, + headers: { + 'content-type': 'application/json', + }, + )); + } +} + +class Spec { + final http.BaseRequest request; + final String method, path; + final Map headers; + final int contentLength; + + Spec(this.request, this.method, this.path, this.headers, this.contentLength); + + @override + String toString() { + return { + 'method': method, + 'path': path, + 'headers': headers, + 'content_length': contentLength, + }.toString(); + } +} diff --git a/test/for_browser_tests.dart b/test/for_browser_tests.dart deleted file mode 100644 index 12378097..00000000 --- a/test/for_browser_tests.dart +++ /dev/null @@ -1,30 +0,0 @@ -const String SERVER = ''' -import 'dart:io'; -import "package:angel_framework/angel_framework.dart"; -import "package:angel_framework/common.dart"; -import 'package:stream_channel/stream_channel.dart'; - -hybridMain(StreamChannel channel) async { - var app = new Angel(); - - app.before.add((req, ResponseContext res) { - res.headers["Access-Control-Allow-Origin"] = "*"; - return true; - }); - - app.use("/todos", new TypedService(new MapService())); - - var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); - - print("Server up; listening at http://localhost:\${server.port}"); - channel.sink.add('http://\${server.address.address}:\${server.port}'); -} - -class Todo extends Model { - String hello; - - Todo({int id, this.hello}) { - this.id = id; - } -} -'''; diff --git a/test/io_test.dart b/test/io_test.dart deleted file mode 100644 index 61c8882e..00000000 --- a/test/io_test.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'dart:io'; -import 'package:angel_client/io.dart' as client; -import 'package:angel_framework/angel_framework.dart' as server; -import 'package:json_god/json_god.dart' as god; -import 'package:test/test.dart'; -import 'shared.dart'; - -main() { - group("rest", () { - server.Angel serverApp = new server.Angel(); - server.HookedService serverPostcards; - client.Angel clientApp; - client.Service clientPostcards; - client.Service clientTypedPostcards; - HttpServer httpServer; - String url; - - setUp(() async { - httpServer = - await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 0); - url = "http://localhost:${httpServer.port}"; - serverApp.use("/postcards", new server.TypedService(new server.MapService())); - serverPostcards = serverApp.service("postcards"); - - clientApp = new client.Rest(url); - clientPostcards = clientApp.service("postcards"); - clientTypedPostcards = clientApp.service("postcards", type: Postcard); - }); - - tearDown(() async { - await httpServer.close(force: true); - }); - - test('plain requests', () async { - final response = await clientApp.get('/foo'); - print(response.body); - }); - - test("index", () async { - Map niagara = await clientPostcards.create( - new Postcard(location: "Niagara Falls", message: "Missing you!")); - Postcard niagaraFalls = new Postcard.fromJson(niagara); - - print('Niagara Falls: ${niagaraFalls.toJson()}'); - - List indexed = await clientPostcards.index(); - print(indexed); - - expect(indexed.length, equals(1)); - expect(indexed[0].keys.length, equals(3)); - expect(indexed[0]['id'], equals(niagaraFalls.id)); - expect(indexed[0]['location'], equals(niagaraFalls.location)); - expect(indexed[0]['message'], equals(niagaraFalls.message)); - - Map l = await clientPostcards.create(new Postcard( - location: "The Louvre", message: "The Mona Lisa was watching me!")); - Postcard louvre = new Postcard.fromJson(l); - print(god.serialize(louvre)); - List typedIndexed = await clientTypedPostcards.index(); - expect(typedIndexed.length, equals(2)); - expect(typedIndexed[1], equals(louvre)); - }); - - test("create/read", () async { - Map opry = {"location": "Grand Ole Opry", "message": "Yeehaw!"}; - var created = await clientPostcards.create(opry); - print(created); - - expect(created['id'] == null, equals(false)); - expect(created["location"], equals(opry["location"])); - expect(created["message"], equals(opry["message"])); - - var read = await clientPostcards.read(created['id']); - print(read); - expect(read['id'], equals(created['id'])); - expect(read['location'], equals(created['location'])); - expect(read['message'], equals(created['message'])); - - Postcard canyon = new Postcard( - location: "Grand Canyon", - message: "But did you REALLY experience it???"); - created = await clientTypedPostcards.create(canyon); - print(god.serialize(created)); - - expect(created.location, equals(canyon.location)); - expect(created.message, equals(canyon.message)); - - read = await clientTypedPostcards.read(created.id); - print(god.serialize(read)); - expect(read.id, equals(created.id)); - expect(read.location, equals(created.location)); - expect(read.message, equals(created.message)); - }); - - test("modify/update", () async { - var innerPostcards = - serverPostcards.inner as server.TypedService; - print(innerPostcards.items); - Postcard mecca = await clientTypedPostcards - .create(new Postcard(location: "Mecca", message: "Pilgrimage")); - print(god.serialize(mecca)); - - // I'm too lazy to write the tests twice, because I know it works - // So I'll modify using the type-based client, and update using the - // map-based one - - print("Postcards on server: " + - god.serialize(await serverPostcards.index())); - print("Postcards on client: " + - god.serialize(await clientPostcards.index())); - - Postcard modified = await clientTypedPostcards - .modify(mecca.id, {"location": "Saudi Arabia"}); - print(god.serialize(modified)); - expect(modified.id, equals(mecca.id)); - expect(modified.location, equals("Saudi Arabia")); - expect(modified.message, equals(mecca.message)); - - Map updated = await clientPostcards - .update(mecca.id, {"location": "Full", "message": "Overwrite"}); - print(updated); - - expect(updated.keys.length, equals(3)); - expect(updated['id'], equals(mecca.id)); - expect(updated['location'], equals("Full")); - expect(updated['message'], equals("Overwrite")); - }); - - test("remove", () async { - Postcard remove1 = await clientTypedPostcards - .create({"location": "remove", "message": "#1"}); - Postcard remove2 = await clientTypedPostcards - .create({"location": "remove", "message": "#2"}); - print(god.serialize([remove1, remove2])); - - Map removed1 = await clientPostcards.remove(remove1.id); - expect(removed1.keys.length, equals(3)); - expect(removed1['id'], equals(remove1.id)); - expect(removed1['location'], equals(remove1.location)); - expect(removed1['message'], equals(remove1.message)); - - Postcard removed2 = await clientTypedPostcards.remove(remove2.id); - expect(removed2, equals(remove2)); - }); - }); -} diff --git a/test/shared.dart b/test/shared.dart index 6d139b4e..7b8af9f6 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -1,4 +1,4 @@ -import "package:angel_framework/common.dart"; +import 'package:angel_model/angel_model.dart'; class Postcard extends Model { String location; From 81c6576458572f96529baa2e20356b563200688a Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 24 Sep 2017 00:15:40 -0400 Subject: [PATCH 43/69] Bye generics --- lib/angel_client.dart | 16 ++++++++-------- lib/base_angel_client.dart | 30 +++++++++++++++--------------- lib/io.dart | 8 ++++---- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/angel_client.dart b/lib/angel_client.dart index e6bde541..5e5f255c 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -44,7 +44,7 @@ abstract class Angel { /// Logs the current user out of the application. Future logout(); - Service service(String path, {Type type, AngelDeserializer deserializer}); + Service service(String path, {Type type, AngelDeserializer deserializer}); Future delete(String url, {Map headers}); @@ -90,24 +90,24 @@ class AngelAuthResult { } /// Queries a service on an Angel server, with the same API. -abstract class Service { +abstract class Service { /// Fired on `indexed` events. - Stream get onIndexed; + Stream get onIndexed; /// Fired on `read` events. - Stream get onRead; + Stream get onRead; /// Fired on `created` events. - Stream get onCreated; + Stream get onCreated; /// Fired on `modified` events. - Stream get onModified; + Stream get onModified; /// Fired on `updated` events. - Stream get onUpdated; + Stream get onUpdated; /// Fired on `removed` events. - Stream get onRemoved; + Stream get onRemoved; /// The Angel instance powering this service. Angel get app; diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 8f9884da..6e97a426 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -191,10 +191,10 @@ abstract class BaseAngelClient extends Angel { } @override - Service service(String path, + Service service(String path, {Type type, AngelDeserializer deserializer}) { String uri = path.toString().replaceAll(straySlashes, ""); - var s = new BaseAngelService(client, this, '$basePath/$uri', + var s = new BaseAngelService(client, this, '$basePath/$uri', deserializer: deserializer); _services.add(s); return s; @@ -241,37 +241,37 @@ abstract class BaseAngelClient extends Angel { } } -class BaseAngelService extends Service { +class BaseAngelService extends Service { @override final BaseAngelClient app; final String basePath; final http.BaseClient client; final AngelDeserializer deserializer; - final StreamController _onIndexed = new StreamController(), - _onRead = new StreamController(), - _onCreated = new StreamController(), - _onModified = new StreamController(), - _onUpdated = new StreamController(), - _onRemoved = new StreamController(); + final StreamController _onIndexed = new StreamController(), + _onRead = new StreamController(), + _onCreated = new StreamController(), + _onModified = new StreamController(), + _onUpdated = new StreamController(), + _onRemoved = new StreamController(); @override - Stream get onIndexed => _onIndexed.stream; + Stream get onIndexed => _onIndexed.stream; @override - Stream get onRead => _onRead.stream; + Stream get onRead => _onRead.stream; @override - Stream get onCreated => _onCreated.stream; + Stream get onCreated => _onCreated.stream; @override - Stream get onModified => _onModified.stream; + Stream get onModified => _onModified.stream; @override - Stream get onUpdated => _onUpdated.stream; + Stream get onUpdated => _onUpdated.stream; @override - Stream get onRemoved => _onRemoved.stream; + Stream get onRemoved => _onRemoved.stream; @override Future close() async { diff --git a/lib/io.dart b/lib/io.dart index 1d450681..e1a34cf7 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -15,10 +15,10 @@ class Rest extends BaseAngelClient { Rest(String path) : super(new http.Client(), path); @override - Service service(String path, {Type type, AngelDeserializer deserializer}) { + Service service(String path, {Type type, AngelDeserializer deserializer}) { String uri = path.replaceAll(straySlashes, ""); - var s = new RestService( - client, this, "$basePath/$uri", T != dynamic ? T : type); + var s = new RestService( + client, this, "$basePath/$uri", type); _services.add(s); return s; } @@ -37,7 +37,7 @@ class Rest extends BaseAngelClient { } /// Queries an Angel service via REST. -class RestService extends BaseAngelService { +class RestService extends BaseAngelService { final Type type; RestService(http.BaseClient client, Angel app, String url, this.type) From d8e8f147050d794e6657fad22614e11f42cf3cba Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 19 Oct 2017 18:16:28 -0400 Subject: [PATCH 44/69] DDC check --- web/index.html | 9 +++++++++ web/main.dart | 8 ++++++++ 2 files changed, 17 insertions(+) create mode 100644 web/index.html create mode 100644 web/main.dart diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..7728ad7e --- /dev/null +++ b/web/index.html @@ -0,0 +1,9 @@ + + + + Client + + + + + \ No newline at end of file diff --git a/web/main.dart b/web/main.dart new file mode 100644 index 00000000..4bfc44f4 --- /dev/null +++ b/web/main.dart @@ -0,0 +1,8 @@ +import 'dart:html'; +import 'package:angel_client/browser.dart'; + +/// Dummy app to ensure client works with DDC. +main() { + var app = new Rest(window.location.origin); + window.alert(app.basePath); +} \ No newline at end of file From 5a6e995b41e5611cb10b83925893eae3afbd4fd7 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 25 Nov 2017 15:58:03 -0500 Subject: [PATCH 45/69] CustomEvent -> Event --- lib/browser.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/browser.dart b/lib/browser.dart index 996eb849..274324d3 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -54,7 +54,7 @@ class Rest extends BaseAngelClient { var wnd = window.open(url, 'angel_client_auth_popup'); Timer t; - StreamSubscription sub; + StreamSubscription sub; t = new Timer.periodic(new Duration(milliseconds: 500), (timer) { if (!ctrl.isClosed) { if (wnd.closed) { diff --git a/pubspec.yaml b/pubspec.yaml index 954111d6..1d0fc339 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.1.0-alpha +version: 1.1.0-alpha+1 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From b30e615a7d18b332ea37495286de6e68358f7276 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 7 Dec 2017 01:41:40 -0500 Subject: [PATCH 46/69] Bump to 1.1.0 --- lib/browser.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/browser.dart b/lib/browser.dart index 274324d3..996eb849 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -54,7 +54,7 @@ class Rest extends BaseAngelClient { var wnd = window.open(url, 'angel_client_auth_popup'); Timer t; - StreamSubscription sub; + StreamSubscription sub; t = new Timer.periodic(new Duration(milliseconds: 500), (timer) { if (!ctrl.isClosed) { if (wnd.closed) { diff --git a/pubspec.yaml b/pubspec.yaml index 1d0fc339..386f028b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.1.0-alpha+1 +version: 1.1.0 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From cd199f2c8f49f42fbc0fb4eb52598e5080d647a5 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 10 Dec 2017 00:13:31 -0500 Subject: [PATCH 47/69] Added ServiceList --- CHANGELOG.md | 2 ++ README.md | 15 +++++++++ lib/angel_client.dart | 78 +++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 3 +- test/list_test.dart | 54 ++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 test/list_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d97c937c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# 1.1.0+1 +Added `ServiceList`. \ No newline at end of file diff --git a/README.md b/README.md index 3c8f1360..6800b328 100644 --- a/README.md +++ b/README.md @@ -89,3 +89,18 @@ 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: + +```dart +build(BuildContext context) async { + var list = new ServiceList(app.service('api/todos')); + + return new StreamBuilder( + stream: list.onChange, + builder: _yourBuildFunction, + ); +} +``` \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 5e5f255c..e65a578d 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -3,6 +3,7 @@ library angel_client; import 'dart:async'; import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:http/src/response.dart' as http; export 'package:angel_http_exception/angel_http_exception.dart'; @@ -132,3 +133,80 @@ abstract class Service { /// Removes the given resource. Future remove(id, [Map params]); } + +/// A [List] that automatically updates itself whenever the referenced [service] fires an event. +class ServiceList extends DelegatingList { + /// 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; + + /// A function used to compare two items for equality. + final bool Function(dynamic, dynamic) compare; + + final Service service; + + final StreamController _onChange = new StreamController(); + final List _subs = []; + + ServiceList(this.service, {this.idField, this.asPaginated: false, this.compare}) : super([]) { + // Index + _subs.add(service.onIndexed.listen((data) { + var items = asPaginated == true ? data['data'] : data; + this..clear()..addAll(items); + })); + + // Created + _subs.add(service.onCreated.listen((item) { + add(item); + _onChange.add(this); + })); + + // Modified/Updated + handleModified(item) { + var indices = []; + + for (int i = 0; i < length; i++) { + if (compareItems(item, this[i])) + indices.add(i); + } + + if (indices.isNotEmpty) { + for (var i in indices) + this[i] = item; + + _onChange.add(this); + } + } + + _subs.addAll([ + service.onModified.listen(handleModified), + service.onUpdated.listen(handleModified), + ]); + + // Removed + _subs.add(service.onRemoved.listen((item) { + removeWhere((x) => compareItems(item, x)); + _onChange.add(this); + })); + } + + /// Fires whenever the underlying [service] fires a change event. + Stream get onChange => _onChange.stream; + + Future close() async { + _onChange.close(); + } + + bool compareItems(a, b) { + if (compare != null) + return compare(a, b); + if (a is Map) + return a[idField ?? 'id'] == b[idField ?? 'id']; + else + return a == b; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 386f028b..7b2512ec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.1.0 +version: 1.1.0+1 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client @@ -7,6 +7,7 @@ environment: sdk: ">=1.21.0" dependencies: angel_http_exception: ^1.0.0 + collection: ^1.0.0 http: ">= 0.11.3 < 0.12.0" json_god: ">=2.0.0-beta <3.0.0" merge_map: ">=1.0.0 <2.0.0" diff --git a/test/list_test.dart b/test/list_test.dart new file mode 100644 index 00000000..0829a3f5 --- /dev/null +++ b/test/list_test.dart @@ -0,0 +1,54 @@ +import 'package:async/async.dart'; +import 'dart:io'; +import 'package:angel_client/io.dart' as c; +import 'package:angel_framework/angel_framework.dart' as s; +import 'package:test/test.dart'; + +main() { + HttpServer server; + c.Angel app; + c.ServiceList list; + StreamQueue queue; + + setUp(() async { + var serverApp = new s.Angel(); + serverApp.use('/api/todos', new s.MapService(autoIdAndDateFields: false)); + + server = await serverApp.startServer(); + var uri = 'http://${server.address.address}:${server.port}'; + app = new c.Rest(uri); + list = new c.ServiceList(app.service('api/todos')); + queue = new StreamQueue(list.onChange); + }); + + tearDown(() async { + await server.close(force: true); + await list.close(); + await list.service.close(); + await app.close(); + }); + + test('listens on create', () async { + list.service.create({'foo': 'bar'}); + await list.onChange.first; + expect(list, [{'foo': 'bar'}]); + }); + + test('listens on modify', () async { + list.service.create({'id': 1, 'foo': 'bar'}); + await queue.next; + + await list.service.update(1, {'id': 1, 'bar': 'baz'}); + await queue.next; + expect(list, [{'id': 1, 'bar': 'baz'}]); + }); + + test('listens on remove', () async { + list.service.create({'id': '1', 'foo': 'bar'}); + await queue.next; + + await list.service.remove('1'); + await queue.next; + expect(list, isEmpty); + }); +} \ No newline at end of file From c52a88048900e829d57a6f3a948a606a5d8427fd Mon Sep 17 00:00:00 2001 From: Tobe O Date: Wed, 13 Dec 2017 01:35:08 -0500 Subject: [PATCH 48/69] ServiceList patch --- CHANGELOG.md | 5 ++++- lib/angel_client.dart | 20 ++++++++++++-------- pubspec.yaml | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d97c937c..5b8a0acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,5 @@ +# 1.1.0+2 +* Updated `ServiceList` to also fire on `index`. + # 1.1.0+1 -Added `ServiceList`. \ No newline at end of file +* Added `ServiceList`. \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart index e65a578d..0464caf9 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -152,11 +152,18 @@ class ServiceList extends DelegatingList { final StreamController _onChange = new StreamController(); final List _subs = []; - ServiceList(this.service, {this.idField, this.asPaginated: false, this.compare}) : super([]) { + ServiceList(this.service, + {this.idField, this.asPaginated: false, this.compare}) + : super([]) { // Index _subs.add(service.onIndexed.listen((data) { var items = asPaginated == true ? data['data'] : data; - this..clear()..addAll(items); + if (items.isNotEmpty) { + this + ..clear() + ..addAll(items); + _onChange.add(this); + } })); // Created @@ -170,13 +177,11 @@ class ServiceList extends DelegatingList { var indices = []; for (int i = 0; i < length; i++) { - if (compareItems(item, this[i])) - indices.add(i); + if (compareItems(item, this[i])) indices.add(i); } if (indices.isNotEmpty) { - for (var i in indices) - this[i] = item; + for (var i in indices) this[i] = item; _onChange.add(this); } @@ -202,8 +207,7 @@ class ServiceList extends DelegatingList { } bool compareItems(a, b) { - if (compare != null) - return compare(a, b); + if (compare != null) return compare(a, b); if (a is Map) return a[idField ?? 'id'] == b[idField ?? 'id']; else diff --git a/pubspec.yaml b/pubspec.yaml index 7b2512ec..0b3adafb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.1.0+1 +version: 1.1.0+2 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From e5fee69cdcd131c805c0f0e072f4c2a6f25a555b Mon Sep 17 00:00:00 2001 From: Tobe O Date: Wed, 13 Dec 2017 11:31:06 -0500 Subject: [PATCH 49/69] ServiceList patch, bump to 1.1.0+3 --- CHANGELOG.md | 3 +++ lib/angel_client.dart | 10 ++++------ pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b8a0acf..167b834d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.1.0+3 +* `ServiceList` no longer ignores empty `index` events. + # 1.1.0+2 * Updated `ServiceList` to also fire on `index`. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 0464caf9..0d412330 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -158,12 +158,10 @@ class ServiceList extends DelegatingList { // Index _subs.add(service.onIndexed.listen((data) { var items = asPaginated == true ? data['data'] : data; - if (items.isNotEmpty) { - this - ..clear() - ..addAll(items); - _onChange.add(this); - } + this + ..clear() + ..addAll(items); + _onChange.add(this); })); // Created diff --git a/pubspec.yaml b/pubspec.yaml index 0b3adafb..6ee4138c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.1.0+2 +version: 1.1.0+3 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 00f20b74b9656404dbd37d54a1e1bb7cc168175e Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 21 Dec 2017 15:08:45 -0500 Subject: [PATCH 50/69] 1.2.0 --- CHANGELOG.md | 4 +++ lib/angel_client.dart | 23 ++++++-------- lib/base_angel_client.dart | 63 +++++++++++++++++++++++++++++--------- pubspec.yaml | 2 +- 4 files changed, 63 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 167b834d..18598df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 * `ServiceList` no longer ignores empty `index` events. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 0d412330..dba4f644 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -144,8 +144,10 @@ class ServiceList extends DelegatingList { /// See https://github.com/angel-dart/paginate. final bool asPaginated; - /// A function used to compare two items for equality. - final bool Function(dynamic, dynamic) compare; + /// A function used to compare the ID's two items for equality. + /// + /// Defaults to comparing the [idField] of `Map` instances. + final Equality _compare; final Service service; @@ -153,8 +155,9 @@ class ServiceList extends DelegatingList { final List _subs = []; ServiceList(this.service, - {this.idField, this.asPaginated: false, this.compare}) - : super([]) { + {this.idField, this.asPaginated: false, Equality compare}) + : _compare = compare ?? new EqualityBy((map) => map[idField ?? 'id']), + super([]) { // Index _subs.add(service.onIndexed.listen((data) { var items = asPaginated == true ? data['data'] : data; @@ -175,7 +178,7 @@ class ServiceList extends DelegatingList { var indices = []; for (int i = 0; i < length; i++) { - if (compareItems(item, this[i])) indices.add(i); + if (_compare.equals(item, this[i])) indices.add(i); } if (indices.isNotEmpty) { @@ -192,7 +195,7 @@ class ServiceList extends DelegatingList { // Removed _subs.add(service.onRemoved.listen((item) { - removeWhere((x) => compareItems(item, x)); + removeWhere((x) => _compare.equals(item, x)); _onChange.add(this); })); } @@ -203,12 +206,4 @@ class ServiceList extends DelegatingList { Future close() async { _onChange.close(); } - - bool compareItems(a, b) { - if (compare != null) return compare(a, b); - if (a is Map) - return a[idField ?? 'id'] == b[idField ?? 'id']; - else - return a == b; - } } diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 6e97a426..527775b3 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -191,8 +191,7 @@ abstract class BaseAngelClient extends Angel { } @override - Service service(String path, - {Type type, AngelDeserializer deserializer}) { + Service service(String path, {Type type, AngelDeserializer deserializer}) { String uri = path.toString().replaceAll(straySlashes, ""); var s = new BaseAngelService(client, this, '$basePath/$uri', deserializer: deserializer); @@ -308,7 +307,10 @@ class BaseAngelService extends Service { try { if (_invalid(response)) { - throw failure(response); + if (_onIndexed.hasListener) + _onIndexed.addError(failure(response)); + else + throw failure(response); } final json = JSON.decode(response.body); @@ -322,7 +324,10 @@ class BaseAngelService extends Service { _onIndexed.add(r); return r; } catch (e, st) { - throw failure(response, error: e, stack: st); + if (_onIndexed.hasListener) + _onIndexed.addError(e, st); + else + throw failure(response, error: e, stack: st); } } @@ -333,14 +338,20 @@ class BaseAngelService extends Service { try { if (_invalid(response)) { - throw failure(response); + if (_onRead.hasListener) + _onRead.addError(failure(response)); + else + throw failure(response); } var r = deserialize(JSON.decode(response.body)); _onRead.add(r); return r; } catch (e, st) { - throw failure(response, error: e, stack: st); + if (_onRead.hasListener) + _onRead.addError(e, st); + else + throw failure(response, error: e, stack: st); } } @@ -351,14 +362,20 @@ class BaseAngelService extends Service { try { if (_invalid(response)) { - throw failure(response); + if (_onCreated.hasListener) + _onCreated.addError(failure(response)); + else + throw failure(response); } var r = deserialize(JSON.decode(response.body)); _onCreated.add(r); return r; } catch (e, st) { - throw failure(response, error: e, stack: st); + if (_onCreated.hasListener) + _onCreated.addError(e, st); + else + throw failure(response, error: e, stack: st); } } @@ -369,14 +386,20 @@ class BaseAngelService extends Service { try { if (_invalid(response)) { - throw failure(response); + if (_onModified.hasListener) + _onModified.addError(failure(response)); + else + throw failure(response); } var r = deserialize(JSON.decode(response.body)); _onModified.add(r); return r; } catch (e, st) { - throw failure(response, error: e, stack: st); + if (_onModified.hasListener) + _onModified.addError(e, st); + else + throw failure(response, error: e, stack: st); } } @@ -387,14 +410,20 @@ class BaseAngelService extends Service { try { if (_invalid(response)) { - throw failure(response); + if (_onUpdated.hasListener) + _onUpdated.addError(failure(response)); + else + throw failure(response); } var r = deserialize(JSON.decode(response.body)); _onUpdated.add(r); return r; } catch (e, st) { - throw failure(response, error: e, stack: st); + if (_onUpdated.hasListener) + _onUpdated.addError(e, st); + else + throw failure(response, error: e, stack: st); } } @@ -405,14 +434,20 @@ class BaseAngelService extends Service { try { if (_invalid(response)) { - throw failure(response); + if (_onRemoved.hasListener) + _onRemoved.addError(failure(response)); + else + throw failure(response); } var r = deserialize(JSON.decode(response.body)); _onRemoved.add(r); return r; } catch (e, st) { - throw failure(response, error: e, stack: st); + if (_onRemoved.hasListener) + _onRemoved.addError(e, st); + else + throw failure(response, error: e, stack: st); } } } diff --git a/pubspec.yaml b/pubspec.yaml index 6ee4138c..4b6a2d57 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.1.0+3 +version: 1.2.0 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 75dda051b4ab1078787f7d62ee88f06b9c72c96e Mon Sep 17 00:00:00 2001 From: Tobe O Date: Wed, 27 Dec 2017 11:00:57 -0500 Subject: [PATCH 51/69] ddc fix --- CHANGELOG.md | 3 +++ lib/browser.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18598df0..6b2dab42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.2.0+1 +* Removed a type annotation in `authenticateViaPopup` to prevent breaking with DDC. + # 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. diff --git a/lib/browser.dart b/lib/browser.dart index 996eb849..19ec2826 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -54,7 +54,7 @@ class Rest extends BaseAngelClient { var wnd = window.open(url, 'angel_client_auth_popup'); Timer t; - StreamSubscription sub; + StreamSubscription sub; t = new Timer.periodic(new Duration(milliseconds: 500), (timer) { if (!ctrl.isClosed) { if (wnd.closed) { diff --git a/pubspec.yaml b/pubspec.yaml index 4b6a2d57..d24af027 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 1.2.0 +version: 1.2.0+1 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From e34d712109d4b83d35df06fbeff71bcc7a062e86 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 22 Jun 2018 20:18:38 -0400 Subject: [PATCH 52/69] More DDC fixes --- CHANGELOG.md | 4 +++ build.yaml | 14 +++++++++ lib/angel_client.dart | 6 ++-- lib/auth_types.dart | 6 +++- lib/base_angel_client.dart | 58 +++++++++++++++++++------------------- lib/browser.dart | 12 ++++---- pubspec.yaml | 13 +++++---- test/all_test.dart | 4 +-- test/common.dart | 6 ++-- test/list_test.dart | 3 +- 10 files changed, 76 insertions(+), 50 deletions(-) create mode 100644 build.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b2dab42..a303c0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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 * Removed a type annotation in `authenticateViaPopup` to prevent breaking with DDC. diff --git a/build.yaml b/build.yaml new file mode 100644 index 00000000..44932516 --- /dev/null +++ b/build.yaml @@ -0,0 +1,14 @@ +targets: + $default: + builders: + build_web_compilers|entrypoint: + generate_for: + - web/**.dart + options: + dart2js_args: + - --dump-info + - --fast-startup + - --minify + - --trust-type-annotations + - --trust-primitives + - --no-source-maps \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart index dba4f644..2d75d071 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -2,8 +2,8 @@ library angel_client; import 'dart:async'; -import 'dart:convert'; import 'package:collection/collection.dart'; +import 'package:dart2_constant/convert.dart'; import 'package:http/src/response.dart' as http; export 'package:angel_http_exception/angel_http_exception.dart'; @@ -82,8 +82,8 @@ class AngelAuthResult { return result; } - factory AngelAuthResult.fromJson(String json) => - new AngelAuthResult.fromMap(JSON.decode(json)); + factory AngelAuthResult.fromJson(String s) => + new AngelAuthResult.fromMap(json.decode(s)); Map toJson() { return {'token': token, 'data': data}; diff --git a/lib/auth_types.dart b/lib/auth_types.dart index 6c57d352..f51ca7d6 100644 --- a/lib/auth_types.dart +++ b/lib/auth_types.dart @@ -1 +1,5 @@ -const String LOCAL = 'local'; \ No newline at end of file +const String local = 'local'; + +/// Use [local] instead. +@deprecated +const String LOCAL = local; \ No newline at end of file diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 527775b3..67508b11 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'dart:convert'; +import 'dart:convert' show Encoding; import 'package:angel_http_exception/angel_http_exception.dart'; -import 'package:collection/collection.dart'; +import 'package:dart2_constant/convert.dart'; 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; @@ -35,10 +35,10 @@ bool _invalid(http.Response response) => AngelHttpException failure(http.Response response, {error, StackTrace stack}) { try { - final json = JSON.decode(response.body); + final v = json.decode(response.body); - if (json is Map && json['isError'] == true) { - return new AngelHttpException.fromMap(json); + if (v is Map && v['isError'] == true) { + return new AngelHttpException.fromMap(v); } else { return new AngelHttpException(error, message: 'Unhandled exception while connecting to Angel backend.', @@ -95,17 +95,17 @@ abstract class BaseAngelClient extends Angel { } try { - final json = JSON.decode(response.body); + final v = json.decode(response.body); - if (json is! Map || - !json.containsKey('data') || - !json.containsKey('token')) { + if (v is! Map || + !v.containsKey('data') || + !v.containsKey('token')) { throw new AngelHttpException.notAuthenticated( message: "Auth endpoint '$url' did not return a proper response."); } - var r = new AngelAuthResult.fromMap(json); + var r = new AngelAuthResult.fromMap(v); _onAuthenticated.add(r); return r; } on AngelHttpException { @@ -119,7 +119,7 @@ abstract class BaseAngelClient extends Angel { if (credentials != null) { response = await client.post(url, - body: JSON.encode(credentials), headers: _writeHeaders); + body: json.encode(credentials), headers: _writeHeaders); } else { response = await client.post(url, headers: _writeHeaders); } @@ -129,17 +129,17 @@ abstract class BaseAngelClient extends Angel { } try { - final json = JSON.decode(response.body); + final v = json.decode(response.body); - if (json is! Map || - !json.containsKey('data') || - !json.containsKey('token')) { + if (v is! Map || + !v.containsKey('data') || + !v.containsKey('token')) { throw new AngelHttpException.notAuthenticated( message: "Auth endpoint '$url' did not return a proper response."); } - var r = new AngelAuthResult.fromMap(json); + var r = new AngelAuthResult.fromMap(v); _onAuthenticated.add(r); return r; } on AngelHttpException { @@ -179,9 +179,9 @@ abstract class BaseAngelClient extends Angel { if (body is String) { request.body = body; } else if (body is List) { - request.bodyBytes = DelegatingList.typed(body); + request.bodyBytes = new List.from(body); } else if (body is Map) { - request.bodyFields = DelegatingMap.typed(body); + request.bodyFields = new Map.from(body); } else { throw new ArgumentError('Invalid request body "$body".'); } @@ -289,7 +289,7 @@ class BaseAngelService extends Service { } makeBody(x) { - return JSON.encode(x); + return json.encode(x); } Future send(http.BaseRequest request) { @@ -313,14 +313,14 @@ class BaseAngelService extends Service { throw failure(response); } - final json = JSON.decode(response.body); + final v = json.decode(response.body); - if (json is! List) { - _onIndexed.add(json); - return json; + if (v is! List) { + _onIndexed.add(v); + return v; } - var r = json.map(deserialize).toList(); + var r = v.map(deserialize).toList(); _onIndexed.add(r); return r; } catch (e, st) { @@ -344,7 +344,7 @@ class BaseAngelService extends Service { throw failure(response); } - var r = deserialize(JSON.decode(response.body)); + var r = deserialize(json.decode(response.body)); _onRead.add(r); return r; } catch (e, st) { @@ -368,7 +368,7 @@ class BaseAngelService extends Service { throw failure(response); } - var r = deserialize(JSON.decode(response.body)); + var r = deserialize(json.decode(response.body)); _onCreated.add(r); return r; } catch (e, st) { @@ -392,7 +392,7 @@ class BaseAngelService extends Service { throw failure(response); } - var r = deserialize(JSON.decode(response.body)); + var r = deserialize(json.decode(response.body)); _onModified.add(r); return r; } catch (e, st) { @@ -416,7 +416,7 @@ class BaseAngelService extends Service { throw failure(response); } - var r = deserialize(JSON.decode(response.body)); + var r = deserialize(json.decode(response.body)); _onUpdated.add(r); return r; } catch (e, st) { @@ -440,7 +440,7 @@ class BaseAngelService extends Service { throw failure(response); } - var r = deserialize(JSON.decode(response.body)); + var r = deserialize(json.decode(response.body)); _onRemoved.add(r); return r; } catch (e, st) { diff --git a/lib/browser.dart b/lib/browser.dart index 19ec2826..a8cb3b0f 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -2,8 +2,8 @@ library angel_client.browser; import 'dart:async' show Future, Stream, StreamController, StreamSubscription, Timer; -import 'dart:convert' show JSON; import 'dart:html' show CustomEvent, Event, window; +import 'package:dart2_constant/convert.dart'; import 'package:http/browser_client.dart' as http; import 'angel_client.dart'; // import 'auth_types.dart' as auth_types; @@ -29,10 +29,10 @@ class Rest extends BaseAngelClient { try { final result = await super.authenticate( type: null, - credentials: {'token': JSON.decode(window.localStorage['token'])}, + credentials: {'token': json.decode(window.localStorage['token'])}, reviveEndpoint: reviveEndpoint); - window.localStorage['token'] = JSON.encode(authToken = result.token); - window.localStorage['user'] = JSON.encode(result.data); + window.localStorage['token'] = json.encode(authToken = result.token); + window.localStorage['user'] = json.encode(result.data); return result; } catch (e, st) { throw new AngelHttpException(e, @@ -41,8 +41,8 @@ class Rest extends BaseAngelClient { } else { final result = await super.authenticate( type: type, credentials: credentials, authEndpoint: authEndpoint); - window.localStorage['token'] = JSON.encode(authToken = result.token); - window.localStorage['user'] = JSON.encode(result.data); + window.localStorage['token'] = json.encode(authToken = result.token); + window.localStorage['user'] = json.encode(result.data); return result; } } diff --git a/pubspec.yaml b/pubspec.yaml index d24af027..fef48fa9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,18 +1,21 @@ name: angel_client -version: 1.2.0+1 +version: 1.2.0+2 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client environment: - sdk: ">=1.21.0" + sdk: ">=1.8.0 <3.0.0" dependencies: angel_http_exception: ^1.0.0 collection: ^1.0.0 - http: ">= 0.11.3 < 0.12.0" + dart2_constant: ^1.0.0 + http: ^0.11.3 json_god: ">=2.0.0-beta <3.0.0" - merge_map: ">=1.0.0 <2.0.0" + merge_map: ^1.0.0 dev_dependencies: angel_framework: ^1.1.0-alpha angel_model: ^1.0.0 + build_runner: ^0.9.0 + build_web_compilers: ^0.4.0 mock_request: ^1.0.0 - test: ">= 0.12.13 < 0.13.0" + test: ^0.12.0 diff --git a/test/all_test.dart b/test/all_test.dart index 2bd2d10b..5a1fded1 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -1,5 +1,5 @@ -import 'dart:convert'; import 'package:angel_client/angel_client.dart'; +import 'package:dart2_constant/convert.dart'; import 'package:test/test.dart'; import 'common.dart'; @@ -87,7 +87,7 @@ main() { .authenticate(type: 'local', credentials: {'username': 'password'}); expect( await read(app.client.spec.request.finalize()), - JSON.encode({'username': 'password'}), + json.encode({'username': 'password'}), ); }); }); diff --git a/test/common.dart b/test/common.dart index 536665a2..2814f788 100644 --- a/test/common.dart +++ b/test/common.dart @@ -1,12 +1,12 @@ import 'dart:async'; -import 'dart:convert'; import 'package:angel_client/base_angel_client.dart'; +import 'package:dart2_constant/convert.dart'; import 'package:http/src/base_client.dart' as http; import 'package:http/src/base_request.dart' as http; import 'package:http/src/streamed_response.dart' as http; Future read(Stream> stream) => - stream.transform(UTF8.decoder).join(); + stream.transform(utf8.decoder).join(); class MockAngel extends BaseAngelClient { @override @@ -38,7 +38,7 @@ class SpecClient extends http.BaseClient { }; return new Future.value(new http.StreamedResponse( - new Stream>.fromIterable([UTF8.encode(JSON.encode(data))]), + new Stream>.fromIterable([utf8.encode(json.encode(data))]), 200, headers: { 'content-type': 'application/json', diff --git a/test/list_test.dart b/test/list_test.dart index 0829a3f5..3198c989 100644 --- a/test/list_test.dart +++ b/test/list_test.dart @@ -12,9 +12,10 @@ main() { setUp(() async { var serverApp = new s.Angel(); + var http = new s.AngelHttp(serverApp); serverApp.use('/api/todos', new s.MapService(autoIdAndDateFields: false)); - server = await serverApp.startServer(); + server = await http.startServer(); var uri = 'http://${server.address.address}:${server.port}'; app = new c.Rest(uri); list = new c.ServiceList(app.service('api/todos')); From f6ef489abf7e6bb60f74a986a4590f157d3a15ad Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 9 Jul 2018 10:46:54 -0400 Subject: [PATCH 53/69] Update .travis.yml --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index de2210c9..0eb6fac6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,4 @@ -language: dart \ No newline at end of file +language: dart +dart: + - dev + - stable From 6ca4fe54eca09f1a4748c84bc3c5f3ed32979f27 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 9 Jul 2018 10:48:01 -0400 Subject: [PATCH 54/69] Update pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index fef48fa9..c73cffb4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: dev_dependencies: angel_framework: ^1.1.0-alpha angel_model: ^1.0.0 - build_runner: ^0.9.0 + build_runner: ">=0.8.0 <0.10.0" build_web_compilers: ^0.4.0 mock_request: ^1.0.0 test: ^0.12.0 From b16ee56f5839143fa0e73a665feb4570f546e689 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 9 Jul 2018 10:52:17 -0400 Subject: [PATCH 55/69] Update pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c73cffb4..baf1ea2e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,6 @@ dev_dependencies: angel_framework: ^1.1.0-alpha angel_model: ^1.0.0 build_runner: ">=0.8.0 <0.10.0" - build_web_compilers: ^0.4.0 + build_web_compilers: ">=0.2.0 <0.5.0" mock_request: ^1.0.0 test: ^0.12.0 From 4a36313c2e941d6f8dee2eff9bb530ad4e9d2822 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 9 Jul 2018 10:55:33 -0400 Subject: [PATCH 56/69] Update pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index baf1ea2e..159deac7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: dev_dependencies: angel_framework: ^1.1.0-alpha angel_model: ^1.0.0 - build_runner: ">=0.8.0 <0.10.0" + build_runner: ">=0.6.0 <0.10.0" build_web_compilers: ">=0.2.0 <0.5.0" mock_request: ^1.0.0 test: ^0.12.0 From ddd5271d1f0c06f1abc8d8a68f68f453e07d260c Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 26 Aug 2018 18:41:01 -0400 Subject: [PATCH 57/69] 2.0.0-alpha --- .gitignore | 2 ++ CHANGELOG.md | 5 +++++ analysis_options.yaml | 3 ++- lib/angel_client.dart | 11 ++++++----- lib/auth_types.dart | 2 +- lib/base_angel_client.dart | 20 ++++++++++---------- lib/browser.dart | 7 ++++--- lib/flutter.dart | 5 +++-- lib/io.dart | 11 ++++++----- pubspec.yaml | 13 ++++++------- test/all_test.dart | 2 +- test/common.dart | 2 +- test/list_test.dart | 10 +++++++--- test/shared.dart | 4 +++- web/main.dart | 2 +- 15 files changed, 58 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index a84c50a4..7752ec67 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,5 @@ jspm_packages # Yarn Integrity file .yarn-integrity + +.dart_tool \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a303c0c6..866e5ba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.0.0-alpha +* Depend on Dart 2. +* Depend on Angel 2. +* Remove `dart2_constant`. + # 1.2.0+2 * Code cleanup + housekeeping, update to `dart2_constant`, and ensured build works with `2.0.0-dev.64.1`. diff --git a/analysis_options.yaml b/analysis_options.yaml index 1325f758..3e203853 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,5 @@ analyzer: - strong-mode: true + strong-mode: + implicit-casts: false exclude: - test/io_test.dart \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 2d75d071..2f89bd8f 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -3,7 +3,7 @@ library angel_client; import 'dart:async'; import 'package:collection/collection.dart'; -import 'package:dart2_constant/convert.dart'; +import 'dart:convert'; import 'package:http/src/response.dart' as http; export 'package:angel_http_exception/angel_http_exception.dart'; @@ -75,15 +75,16 @@ class AngelAuthResult { final result = new AngelAuthResult(); if (data is Map && data.containsKey('token') && data['token'] is String) - result._token = data['token']; + result._token = data['token'].toString(); - if (data is Map) result.data.addAll(data['data'] ?? {}); + if (data is Map) + result.data.addAll((data['data'] as Map) ?? {}); return result; } factory AngelAuthResult.fromJson(String s) => - new AngelAuthResult.fromMap(json.decode(s)); + new AngelAuthResult.fromMap(json.decode(s) as Map); Map toJson() { return {'token': token, 'data': data}; @@ -163,7 +164,7 @@ class ServiceList extends DelegatingList { var items = asPaginated == true ? data['data'] : data; this ..clear() - ..addAll(items); + ..addAll(items as Iterable); _onChange.add(this); })); diff --git a/lib/auth_types.dart b/lib/auth_types.dart index f51ca7d6..f28a06af 100644 --- a/lib/auth_types.dart +++ b/lib/auth_types.dart @@ -2,4 +2,4 @@ const String local = 'local'; /// Use [local] instead. @deprecated -const String LOCAL = local; \ No newline at end of file +const String LOCAL = local; diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 67508b11..96fb0308 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert' show Encoding; import 'package:angel_http_exception/angel_http_exception.dart'; -import 'package:dart2_constant/convert.dart'; +import 'dart:convert'; 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; @@ -77,7 +77,7 @@ abstract class BaseAngelClient extends Angel { if (credentials is String) token = credentials; else if (credentials is Map && credentials.containsKey('token')) - token = credentials['token']; + token = credentials['token'].toString(); if (token == null) { throw new ArgumentError( @@ -98,14 +98,14 @@ abstract class BaseAngelClient extends Angel { final v = json.decode(response.body); if (v is! Map || - !v.containsKey('data') || - !v.containsKey('token')) { + !(v as Map).containsKey('data') || + !(v as Map).containsKey('token')) { throw new AngelHttpException.notAuthenticated( message: "Auth endpoint '$url' did not return a proper response."); } - var r = new AngelAuthResult.fromMap(v); + var r = new AngelAuthResult.fromMap(v as Map); _onAuthenticated.add(r); return r; } on AngelHttpException { @@ -132,14 +132,14 @@ abstract class BaseAngelClient extends Angel { final v = json.decode(response.body); if (v is! Map || - !v.containsKey('data') || - !v.containsKey('token')) { + !(v as Map).containsKey('data') || + !(v as Map).containsKey('token')) { throw new AngelHttpException.notAuthenticated( message: "Auth endpoint '$url' did not return a proper response."); } - var r = new AngelAuthResult.fromMap(v); + var r = new AngelAuthResult.fromMap(v as Map); _onAuthenticated.add(r); return r; } on AngelHttpException { @@ -166,8 +166,8 @@ abstract class BaseAngelClient extends Angel { Future sendUnstreamed( String method, url, Map headers, [body, Encoding encoding]) async { - if (url is String) url = Uri.parse(url); - var request = new http.Request(method, url); + var request = + new http.Request(method, url is Uri ? url : Uri.parse(url.toString())); if (headers != null) request.headers.addAll(headers); diff --git a/lib/browser.dart b/lib/browser.dart index a8cb3b0f..54b67a80 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -1,9 +1,10 @@ /// Browser client library for the Angel framework. library angel_client.browser; -import 'dart:async' show Future, Stream, StreamController, StreamSubscription, Timer; +import 'dart:async' + show Future, Stream, StreamController, StreamSubscription, Timer; import 'dart:html' show CustomEvent, Event, window; -import 'package:dart2_constant/convert.dart'; +import 'dart:convert'; import 'package:http/browser_client.dart' as http; import 'angel_client.dart'; // import 'auth_types.dart' as auth_types; @@ -72,7 +73,7 @@ class Rest extends BaseAngelClient { sub = window.on[eventName ?? 'token'].listen((Event ev) { var e = ev as CustomEvent; if (!ctrl.isClosed) { - ctrl.add(e.detail); + ctrl.add(e.detail.toString()); t.cancel(); ctrl.close(); sub.cancel(); diff --git a/lib/flutter.dart b/lib/flutter.dart index 3b407932..83bfec5e 100644 --- a/lib/flutter.dart +++ b/lib/flutter.dart @@ -8,10 +8,11 @@ export 'angel_client.dart'; /// Queries an Angel server via REST. class Rest extends BaseAngelClient { - Rest(String basePath) : super(new http.Client(), basePath); + Rest(String basePath) : super(new http.Client() as http.BaseClient, basePath); @override Stream authenticateViaPopup(String url, {String eventName: 'token'}) { - throw new UnimplementedError('Opening popup windows is not supported in the `dart:io` client.'); + throw new UnimplementedError( + 'Opening popup windows is not supported in the `dart:io` client.'); } } diff --git a/lib/io.dart b/lib/io.dart index e1a34cf7..538d9fe6 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -12,20 +12,20 @@ export 'angel_client.dart'; class Rest extends BaseAngelClient { final List _services = []; - Rest(String path) : super(new http.Client(), path); + Rest(String path) : super(new http.Client() as http.BaseClient, path); @override Service service(String path, {Type type, AngelDeserializer deserializer}) { String uri = path.replaceAll(straySlashes, ""); - var s = new RestService( - client, this, "$basePath/$uri", type); + var s = new RestService(client, this, "$basePath/$uri", type); _services.add(s); return s; } @override Stream authenticateViaPopup(String url, {String eventName: 'token'}) { - throw new UnimplementedError('Opening popup windows is not supported in the `dart:io` client.'); + throw new UnimplementedError( + 'Opening popup windows is not supported in the `dart:io` client.'); } Future close() async { @@ -40,7 +40,8 @@ class Rest extends BaseAngelClient { class RestService extends BaseAngelService { final Type type; - RestService(http.BaseClient client, Angel app, String url, this.type) + RestService( + http.BaseClient client, BaseAngelClient app, String url, this.type) : super(client, app, url); @override diff --git a/pubspec.yaml b/pubspec.yaml index 159deac7..2870cf10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,21 +1,20 @@ name: angel_client -version: 1.2.0+2 +version: 2.0.0-alpha description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client environment: - sdk: ">=1.8.0 <3.0.0" + sdk: ">=2.0.0-dev <3.0.0" dependencies: angel_http_exception: ^1.0.0 collection: ^1.0.0 - dart2_constant: ^1.0.0 http: ^0.11.3 json_god: ">=2.0.0-beta <3.0.0" merge_map: ^1.0.0 dev_dependencies: - angel_framework: ^1.1.0-alpha + angel_framework: ^2.0.0-alpha angel_model: ^1.0.0 - build_runner: ">=0.6.0 <0.10.0" - build_web_compilers: ">=0.2.0 <0.5.0" + build_runner: ^0.10.0 + build_web_compilers: ^0.4.0 mock_request: ^1.0.0 - test: ^0.12.0 + test: ^1.0.0 diff --git a/test/all_test.dart b/test/all_test.dart index 5a1fded1..4816d38e 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -1,5 +1,5 @@ import 'package:angel_client/angel_client.dart'; -import 'package:dart2_constant/convert.dart'; +import 'dart:convert'; import 'package:test/test.dart'; import 'common.dart'; diff --git a/test/common.dart b/test/common.dart index 2814f788..3a7921d5 100644 --- a/test/common.dart +++ b/test/common.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'package:angel_client/base_angel_client.dart'; -import 'package:dart2_constant/convert.dart'; +import 'dart:convert'; import 'package:http/src/base_client.dart' as http; import 'package:http/src/base_request.dart' as http; import 'package:http/src/streamed_response.dart' as http; diff --git a/test/list_test.dart b/test/list_test.dart index 3198c989..26c934c5 100644 --- a/test/list_test.dart +++ b/test/list_test.dart @@ -32,7 +32,9 @@ main() { test('listens on create', () async { list.service.create({'foo': 'bar'}); await list.onChange.first; - expect(list, [{'foo': 'bar'}]); + expect(list, [ + {'foo': 'bar'} + ]); }); test('listens on modify', () async { @@ -41,7 +43,9 @@ main() { await list.service.update(1, {'id': 1, 'bar': 'baz'}); await queue.next; - expect(list, [{'id': 1, 'bar': 'baz'}]); + expect(list, [ + {'id': 1, 'bar': 'baz'} + ]); }); test('listens on remove', () async { @@ -52,4 +56,4 @@ main() { await queue.next; expect(list, isEmpty); }); -} \ No newline at end of file +} diff --git a/test/shared.dart b/test/shared.dart index 7b8af9f6..11b6c660 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -9,7 +9,9 @@ class Postcard extends Model { } factory Postcard.fromJson(Map data) => new Postcard( - id: data['id'], location: data['location'], message: data['message']); + id: data['id'].toString(), + location: data['location'].toString(), + message: data['message'].toString()); @override bool operator ==(other) { diff --git a/web/main.dart b/web/main.dart index 4bfc44f4..4cd7910d 100644 --- a/web/main.dart +++ b/web/main.dart @@ -5,4 +5,4 @@ import 'package:angel_client/browser.dart'; main() { var app = new Rest(window.location.origin); window.alert(app.basePath); -} \ No newline at end of file +} From 8b67f8d4bb4cd138faa2578c567bb6ba5c30ea2d Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 2 Oct 2018 11:42:26 -0400 Subject: [PATCH 58/69] Refactor `params` to `Map`. --- CHANGELOG.md | 3 +++ lib/angel_client.dart | 42 +++++++++++++++++++------------------ lib/base_angel_client.dart | 43 +++++++++++++++++++------------------- lib/io.dart | 15 ++++++------- pubspec.yaml | 2 +- 5 files changed, 56 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 866e5ba8..b78b1bb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.0.0-alpha.1 +* Refactor `params` to `Map`. + # 2.0.0-alpha * Depend on Dart 2. * Depend on Angel 2. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index 2f89bd8f..fa1ff0bf 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -8,13 +8,13 @@ import 'package:http/src/response.dart' as http; export 'package:angel_http_exception/angel_http_exception.dart'; /// A function that configures an [Angel] client in some way. -typedef Future AngelConfigurer(Angel app); +typedef FutureOr AngelConfigurer(Angel app); /// A function that deserializes data received from the server. /// /// This is only really necessary in the browser, where `json_god` /// doesn't work. -typedef AngelDeserializer(x); +typedef T AngelDeserializer(x); /// Represents an Angel server that we are querying. abstract class Angel { @@ -45,7 +45,8 @@ abstract class Angel { /// Logs the current user out of the application. Future logout(); - Service service(String path, {Type type, AngelDeserializer deserializer}); + Service service(String path, + {Type type, AngelDeserializer deserializer}); Future delete(String url, {Map headers}); @@ -92,24 +93,24 @@ class AngelAuthResult { } /// Queries a service on an Angel server, with the same API. -abstract class Service { +abstract class Service { /// Fired on `indexed` events. Stream get onIndexed; /// Fired on `read` events. - Stream get onRead; + Stream get onRead; /// Fired on `created` events. - Stream get onCreated; + Stream get onCreated; /// Fired on `modified` events. - Stream get onModified; + Stream get onModified; /// Fired on `updated` events. - Stream get onUpdated; + Stream get onUpdated; /// Fired on `removed` events. - Stream get onRemoved; + Stream get onRemoved; /// The Angel instance powering this service. Angel get app; @@ -117,26 +118,26 @@ abstract class Service { Future close(); /// Retrieves all resources. - Future index([Map params]); + Future index([Map params]); /// Retrieves the desired resource. - Future read(id, [Map params]); + Future read(Id id, [Map params]); /// Creates a resource. - Future create(data, [Map params]); + Future create(Data data, [Map params]); /// Modifies a resource. - Future modify(id, data, [Map params]); + Future modify(Id id, Data data, [Map params]); /// Overwrites a resource. - Future update(id, data, [Map params]); + Future update(Id id, Data data, [Map params]); /// Removes the given resource. - Future remove(id, [Map params]); + Future remove(Id id, [Map params]); } /// A [List] that automatically updates itself whenever the referenced [service] fires an event. -class ServiceList extends DelegatingList { +class ServiceList extends DelegatingList { /// A field name used to compare [Map] by ID. final String idField; @@ -150,9 +151,10 @@ class ServiceList extends DelegatingList { /// Defaults to comparing the [idField] of `Map` instances. final Equality _compare; - final Service service; + final Service service; - final StreamController _onChange = new StreamController(); + final StreamController> _onChange = + new StreamController(); final List _subs = []; ServiceList(this.service, @@ -175,7 +177,7 @@ class ServiceList extends DelegatingList { })); // Modified/Updated - handleModified(item) { + handleModified(Data item) { var indices = []; for (int i = 0; i < length; i++) { @@ -202,7 +204,7 @@ class ServiceList extends DelegatingList { } /// Fires whenever the underlying [service] fires a change event. - Stream get onChange => _onChange.stream; + Stream> get onChange => _onChange.stream; Future close() async { _onChange.close(); diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 96fb0308..13f7f79b 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -16,7 +16,7 @@ const Map _writeHeaders = const { 'Content-Type': 'application/json' }; -_buildQuery(Map params) { +_buildQuery(Map params) { if (params == null || params.isEmpty || params['query'] is! Map) return ""; List query = []; @@ -191,9 +191,10 @@ abstract class BaseAngelClient extends Angel { } @override - Service service(String path, {Type type, AngelDeserializer deserializer}) { + Service service(String path, + {Type type, AngelDeserializer deserializer}) { String uri = path.toString().replaceAll(straySlashes, ""); - var s = new BaseAngelService(client, this, '$basePath/$uri', + var s = new BaseAngelService(client, this, '$basePath/$uri', deserializer: deserializer); _services.add(s); return s; @@ -240,15 +241,15 @@ abstract class BaseAngelClient extends Angel { } } -class BaseAngelService extends Service { +class BaseAngelService extends Service { @override final BaseAngelClient app; final String basePath; final http.BaseClient client; - final AngelDeserializer deserializer; + final AngelDeserializer deserializer; - final StreamController _onIndexed = new StreamController(), - _onRead = new StreamController(), + final StreamController _onIndexed = new StreamController(); + final StreamController _onRead = new StreamController(), _onCreated = new StreamController(), _onModified = new StreamController(), _onUpdated = new StreamController(), @@ -258,19 +259,19 @@ class BaseAngelService extends Service { Stream get onIndexed => _onIndexed.stream; @override - Stream get onRead => _onRead.stream; + Stream get onRead => _onRead.stream; @override - Stream get onCreated => _onCreated.stream; + Stream get onCreated => _onCreated.stream; @override - Stream get onModified => _onModified.stream; + Stream get onModified => _onModified.stream; @override - Stream get onUpdated => _onUpdated.stream; + Stream get onUpdated => _onUpdated.stream; @override - Stream get onRemoved => _onRemoved.stream; + Stream get onRemoved => _onRemoved.stream; @override Future close() async { @@ -284,8 +285,8 @@ class BaseAngelService extends Service { BaseAngelService(this.client, this.app, this.basePath, {this.deserializer}); - deserialize(x) { - return deserializer != null ? deserializer(x) : x; + Data deserialize(x) { + return deserializer != null ? deserializer(x) : x as Data; } makeBody(x) { @@ -301,7 +302,7 @@ class BaseAngelService extends Service { } @override - Future index([Map params]) async { + Future index([Map params]) async { final response = await app.sendUnstreamed( 'GET', '$basePath${_buildQuery(params)}', _readHeaders); @@ -316,7 +317,7 @@ class BaseAngelService extends Service { final v = json.decode(response.body); if (v is! List) { - _onIndexed.add(v); + _onIndexed.add(v as Data); return v; } @@ -332,7 +333,7 @@ class BaseAngelService extends Service { } @override - Future read(id, [Map params]) async { + Future read(id, [Map params]) async { final response = await app.sendUnstreamed( 'GET', '$basePath/$id${_buildQuery(params)}', _readHeaders); @@ -356,7 +357,7 @@ class BaseAngelService extends Service { } @override - Future create(data, [Map params]) async { + Future create(data, [Map params]) async { final response = await app.sendUnstreamed('POST', '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data)); @@ -380,7 +381,7 @@ class BaseAngelService extends Service { } @override - Future modify(id, data, [Map params]) async { + Future modify(id, data, [Map params]) async { final response = await app.sendUnstreamed('PATCH', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); @@ -404,7 +405,7 @@ class BaseAngelService extends Service { } @override - Future update(id, data, [Map params]) async { + Future update(id, data, [Map params]) async { final response = await app.sendUnstreamed('POST', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); @@ -428,7 +429,7 @@ class BaseAngelService extends Service { } @override - Future remove(id, [Map params]) async { + Future remove(id, [Map params]) async { final response = await app.sendUnstreamed( 'DELETE', '$basePath/$id${_buildQuery(params)}', _readHeaders); diff --git a/lib/io.dart b/lib/io.dart index 538d9fe6..cb50df37 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -15,9 +15,10 @@ class Rest extends BaseAngelClient { Rest(String path) : super(new http.Client() as http.BaseClient, path); @override - Service service(String path, {Type type, AngelDeserializer deserializer}) { + Service service(String path, + {Type type, AngelDeserializer deserializer}) { String uri = path.replaceAll(straySlashes, ""); - var s = new RestService(client, this, "$basePath/$uri", type); + var s = new RestService(client, this, "$basePath/$uri", type); _services.add(s); return s; } @@ -37,7 +38,7 @@ class Rest extends BaseAngelClient { } /// Queries an Angel service via REST. -class RestService extends BaseAngelService { +class RestService extends BaseAngelService { final Type type; RestService( @@ -45,14 +46,14 @@ class RestService extends BaseAngelService { : super(client, app, url); @override - deserialize(x) { + Data deserialize(x) { if (type != null) { return x.runtimeType == type - ? x - : god.deserializeDatum(x, outputType: type); + ? x as Data + : god.deserializeDatum(x, outputType: type) as Data; } - return x; + return x as Data; } @override diff --git a/pubspec.yaml b/pubspec.yaml index 2870cf10..dbab4f54 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 2.0.0-alpha +version: 2.0.0-alpha.1 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 58eafd36c5a719bf0335b867ca9782bdc21e50ca Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 22 Oct 2018 19:05:11 -0400 Subject: [PATCH 59/69] explicit http --- test/list_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/list_test.dart b/test/list_test.dart index 26c934c5..29beb3bf 100644 --- a/test/list_test.dart +++ b/test/list_test.dart @@ -2,6 +2,7 @@ import 'package:async/async.dart'; import 'dart:io'; import 'package:angel_client/io.dart' as c; import 'package:angel_framework/angel_framework.dart' as s; +import 'package:angel_framework/http.dart' as s; import 'package:test/test.dart'; main() { From c2c4ae6d6bb0afa83c57c1365266557a68880d33 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 3 Nov 2018 20:43:56 -0400 Subject: [PATCH 60/69] Expand http constraint --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index dbab4f54..5e8aa7b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 2.0.0-alpha.1 +version: 2.0.0-alpha.2 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client @@ -8,7 +8,7 @@ environment: dependencies: angel_http_exception: ^1.0.0 collection: ^1.0.0 - http: ^0.11.3 + http: ">=0.11.0 <2.0.0" json_god: ">=2.0.0-beta <3.0.0" merge_map: ^1.0.0 dev_dependencies: From ca68427a211e9522d6a151743bf8d4712a0588b4 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 3 Nov 2018 21:34:21 -0400 Subject: [PATCH 61/69] 2.0.0-alpha.2 --- CHANGELOG.md | 4 ++ lib/angel_client.dart | 131 +++++++++++++++++++++++++++++-------- lib/base_angel_client.dart | 49 ++++++++------ test/common.dart | 7 +- 4 files changed, 141 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b78b1bb3..b5c510a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.0-alpha.2 +* Make Service `index` always return `List`. +* Add `Service.map`. + # 2.0.0-alpha.1 * Refactor `params` to `Map`. diff --git a/lib/angel_client.dart b/lib/angel_client.dart index fa1ff0bf..edcc52d1 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -8,7 +8,7 @@ import 'package:http/src/response.dart' as http; export 'package:angel_http_exception/angel_http_exception.dart'; /// A function that configures an [Angel] client in some way. -typedef FutureOr AngelConfigurer(Angel app); +typedef Future AngelConfigurer(Angel app); /// A function that deserializes data received from the server. /// @@ -95,7 +95,7 @@ class AngelAuthResult { /// Queries a service on an Angel server, with the same API. abstract class Service { /// Fired on `indexed` events. - Stream get onIndexed; + Stream> get onIndexed; /// Fired on `read` events. Stream get onRead; @@ -118,60 +118,133 @@ abstract class Service { Future close(); /// Retrieves all resources. - Future index([Map params]); + Future> index([Map params]); /// Retrieves the desired resource. - Future read(Id id, [Map params]); + Future read(Id id, [Map params]); /// Creates a resource. - Future create(Data data, [Map params]); + Future create(Data data, [Map params]); /// Modifies a resource. - Future modify(Id id, Data data, [Map params]); + Future modify(Id id, Data data, [Map params]); /// Overwrites a resource. - Future update(Id id, Data data, [Map params]); + Future update(Id id, Data data, [Map params]); /// Removes the given resource. - Future remove(Id id, [Map params]); + Future remove(Id id, [Map params]); + + /// Creates a [Service] that wraps over this one, and maps input and output using two converter functions. + /// + /// Handy utility for handling data in a type-safe manner. + Service map(U Function(Data) encoder, Data Function(U) decoder) { + return new _MappedService(this, encoder, decoder); + } +} + +class _MappedService extends Service { + final Service inner; + final U Function(Data) encoder; + final Data Function(U) decoder; + + _MappedService(this.inner, this.encoder, this.decoder); + + @override + Angel get app => inner.app; + + @override + Future close() => new Future.value(); + + @override + Future create(U data, [Map params]) { + return inner.create(decoder(data)).then(encoder); + } + + @override + Future> index([Map params]) { + return inner.index(params).then((l) => l.map(encoder).toList()); + } + + @override + Future modify(Id id, U data, [Map params]) { + return inner.modify(id, decoder(data), params).then(encoder); + } + + @override + Stream get onCreated => inner.onCreated.map(encoder); + + @override + Stream> get onIndexed => + inner.onIndexed.map((l) => l.map(encoder).toList()); + + @override + Stream get onModified => inner.onModified.map(encoder); + + @override + Stream get onRead => inner.onRead.map(encoder); + + @override + Stream get onRemoved => inner.onRemoved.map(encoder); + + @override + Stream get onUpdated => inner.onUpdated.map(encoder); + + @override + Future read(Id id, [Map params]) { + return inner.read(id, params).then(encoder); + } + + @override + Future remove(Id id, [Map params]) { + return inner.remove(id, params).then(encoder); + } + + @override + Future update(Id id, U data, [Map params]) { + return inner.update(id, decoder(data), params).then(encoder); + } } /// A [List] that automatically updates itself whenever the referenced [service] fires an event. -class ServiceList extends DelegatingList { +class ServiceList extends DelegatingList { /// 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; - /// A function used to compare the ID's two items for equality. /// /// Defaults to comparing the [idField] of `Map` instances. - final Equality _compare; + Equality get equality => _equality; + + Equality _equality; final Service service; final StreamController> _onChange = new StreamController(); + final List _subs = []; - ServiceList(this.service, - {this.idField, this.asPaginated: false, Equality compare}) - : _compare = compare ?? new EqualityBy((map) => map[idField ?? 'id']), - super([]) { + ServiceList(this.service, {this.idField, Equality equality}) + : super([]) { + _equality = equality; + _equality ??= new EqualityBy((map) { + if (map is Map) + return map[idField ?? 'id'] as Id; + else + throw new UnsupportedError( + 'ServiceList only knows how to find the id from a Map object. Provide a custom `Equality` in your call to the constructor.'); + }); // Index - _subs.add(service.onIndexed.listen((data) { - var items = asPaginated == true ? data['data'] : data; + _subs.add(service.onIndexed.where(_notNull).listen((data) { this ..clear() - ..addAll(items as Iterable); + ..addAll(data); _onChange.add(this); })); // Created - _subs.add(service.onCreated.listen((item) { + _subs.add(service.onCreated.where(_notNull).listen((item) { add(item); _onChange.add(this); })); @@ -181,7 +254,7 @@ class ServiceList extends DelegatingList { var indices = []; for (int i = 0; i < length; i++) { - if (_compare.equals(item, this[i])) indices.add(i); + if (_equality.equals(item, this[i])) indices.add(i); } if (indices.isNotEmpty) { @@ -192,17 +265,19 @@ class ServiceList extends DelegatingList { } _subs.addAll([ - service.onModified.listen(handleModified), - service.onUpdated.listen(handleModified), + service.onModified.where(_notNull).listen(handleModified), + service.onUpdated.where(_notNull).listen(handleModified), ]); // Removed - _subs.add(service.onRemoved.listen((item) { - removeWhere((x) => _compare.equals(item, x)); + _subs.add(service.onRemoved.where(_notNull).listen((item) { + removeWhere((x) => _equality.equals(item, x)); _onChange.add(this); })); } + static bool _notNull(x) => x != null; + /// Fires whenever the underlying [service] fires a change event. Stream> get onChange => _onChange.stream; diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 13f7f79b..4704085a 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -33,21 +33,24 @@ bool _invalid(http.Response response) => response.statusCode < 200 || response.statusCode >= 300; -AngelHttpException failure(http.Response response, {error, StackTrace stack}) { +AngelHttpException failure(http.Response response, + {error, String message, StackTrace stack}) { try { final v = json.decode(response.body); - if (v is Map && v['isError'] == true) { - return new AngelHttpException.fromMap(v); + if (v is Map && (v['is_error'] == true) || v['isError'] == true) { + return new AngelHttpException.fromMap(v as Map); } else { return new AngelHttpException(error, - message: 'Unhandled exception while connecting to Angel backend.', + message: message ?? + 'Unhandled exception while connecting to Angel backend.', statusCode: response.statusCode, stackTrace: stack); } } catch (e, st) { return new AngelHttpException(error ?? e, - message: 'Unhandled exception while connecting to Angel backend.', + message: message ?? + 'Angel backend did not return JSON - an error likely occurred.', statusCode: response.statusCode, stackTrace: stack ?? st); } @@ -248,7 +251,7 @@ class BaseAngelService extends Service { final http.BaseClient client; final AngelDeserializer deserializer; - final StreamController _onIndexed = new StreamController(); + final StreamController> _onIndexed = new StreamController(); final StreamController _onRead = new StreamController(), _onCreated = new StreamController(), _onModified = new StreamController(), @@ -256,7 +259,7 @@ class BaseAngelService extends Service { _onRemoved = new StreamController(); @override - Stream get onIndexed => _onIndexed.stream; + Stream> get onIndexed => _onIndexed.stream; @override Stream get onRead => _onRead.stream; @@ -302,7 +305,7 @@ class BaseAngelService extends Service { } @override - Future index([Map params]) async { + Future> index([Map params]) async { final response = await app.sendUnstreamed( 'GET', '$basePath${_buildQuery(params)}', _readHeaders); @@ -314,13 +317,7 @@ class BaseAngelService extends Service { throw failure(response); } - final v = json.decode(response.body); - - if (v is! List) { - _onIndexed.add(v as Data); - return v; - } - + final v = json.decode(response.body) as List; var r = v.map(deserialize).toList(); _onIndexed.add(r); return r; @@ -330,10 +327,12 @@ class BaseAngelService extends Service { else throw failure(response, error: e, stack: st); } + + return null; } @override - Future read(id, [Map params]) async { + Future read(id, [Map params]) async { final response = await app.sendUnstreamed( 'GET', '$basePath/$id${_buildQuery(params)}', _readHeaders); @@ -354,10 +353,12 @@ class BaseAngelService extends Service { else throw failure(response, error: e, stack: st); } + + return null; } @override - Future create(data, [Map params]) async { + Future create(data, [Map params]) async { final response = await app.sendUnstreamed('POST', '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data)); @@ -378,10 +379,12 @@ class BaseAngelService extends Service { else throw failure(response, error: e, stack: st); } + + return null; } @override - Future modify(id, data, [Map params]) async { + Future modify(id, data, [Map params]) async { final response = await app.sendUnstreamed('PATCH', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); @@ -402,10 +405,12 @@ class BaseAngelService extends Service { else throw failure(response, error: e, stack: st); } + + return null; } @override - Future update(id, data, [Map params]) async { + Future update(id, data, [Map params]) async { final response = await app.sendUnstreamed('POST', '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); @@ -426,10 +431,12 @@ class BaseAngelService extends Service { else throw failure(response, error: e, stack: st); } + + return null; } @override - Future remove(id, [Map params]) async { + Future remove(id, [Map params]) async { final response = await app.sendUnstreamed( 'DELETE', '$basePath/$id${_buildQuery(params)}', _readHeaders); @@ -450,5 +457,7 @@ class BaseAngelService extends Service { else throw failure(response, error: e, stack: st); } + + return null; } } diff --git a/test/common.dart b/test/common.dart index 3a7921d5..bc54f5cf 100644 --- a/test/common.dart +++ b/test/common.dart @@ -29,13 +29,16 @@ class SpecClient extends http.BaseClient { send(http.BaseRequest request) { _spec = new Spec(request, request.method, request.url.path, request.headers, request.contentLength); - var data = {'text': 'Clean your room!', 'completed': true}; + dynamic data = {'text': 'Clean your room!', 'completed': true}; - if (request.url.path.contains('auth')) + if (request.url.path.contains('auth')) { data = { 'token': '', 'data': {'username': 'password'} }; + } else if (request.url.path == '/api/todos' && request.method == 'GET') { + data = [data]; + } return new Future.value(new http.StreamedResponse( new Stream>.fromIterable([utf8.encode(json.encode(data))]), From 7652a998b758b1e40f2db779c5d59bf2f67a0199 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 5 Jan 2019 21:08:29 -0500 Subject: [PATCH 62/69] 2.0.0 --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a2eb533abf551d042629249e6fe5fb378b3b5ee0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~u?oUK42Bc!Ah>jNyu}Cb4Gz&K=nFU~E>c0O^F6wMazU^xC+1b{XuyJ79K1T}(S!u1*@b}wNMJ-@TJzTK|1JE}{6A`8N&+PC zX9Tp_belC^D(=>|*R%RAs Date: Sat, 5 Jan 2019 21:08:31 -0500 Subject: [PATCH 63/69] 2.0.0 --- CHANGELOG.md | 5 + analysis_options.yaml | 5 +- example/main.dart | 21 ++++ lib/angel_client.dart | 102 ++++++++++++---- lib/auth_types.dart | 7 +- lib/base_angel_client.dart | 235 +++++++++++++++++-------------------- lib/browser.dart | 35 ++---- lib/flutter.dart | 5 +- lib/io.dart | 11 +- pubspec.yaml | 11 +- test/all_test.dart | 11 +- test/common.dart | 2 +- web/main.dart | 2 +- 13 files changed, 248 insertions(+), 204 deletions(-) create mode 100644 example/main.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index b5c510a5..6cb5ffd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.0.0 +* Deprecate `basePath` in favor of `baseUrl`. +* `Angel` now extends `http.Client`. +* Deprecate `auth_types`. + # 2.0.0-alpha.2 * Make Service `index` always return `List`. * Add `Service.map`. diff --git a/analysis_options.yaml b/analysis_options.yaml index 3e203853..a146a9e6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,5 +1,6 @@ +include: package:pedantic/analysis_options.yaml analyzer: strong-mode: implicit-casts: false - exclude: - - test/io_test.dart \ No newline at end of file + errors: + unawaited_futures: ignore \ No newline at end of file diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 00000000..d237c4bb --- /dev/null +++ b/example/main.dart @@ -0,0 +1,21 @@ +import 'dart:async'; +import 'package:angel_client/angel_client.dart'; + +Future doSomething(Angel app) async { + var userService = app + .service>('api/users') + .map(User.fromMap, User.toMap); + + var users = await userService.index(); + print('Name: ${users.first.name}'); +} + +class User { + final String name; + + User({this.name}); + + static User fromMap(Map data) => User(name: data['name'] as String); + + static Map toMap(User user) => {'name': user.name}; +} diff --git a/lib/angel_client.dart b/lib/angel_client.dart index edcc52d1..f681786b 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -4,11 +4,12 @@ library angel_client; import 'dart:async'; import 'package:collection/collection.dart'; import 'dart:convert'; -import 'package:http/src/response.dart' as http; +import 'package:http/http.dart' as http; export 'package:angel_http_exception/angel_http_exception.dart'; +import 'package:meta/meta.dart'; /// A function that configures an [Angel] client in some way. -typedef Future AngelConfigurer(Angel app); +typedef FutureOr AngelConfigurer(Angel app); /// A function that deserializes data received from the server. /// @@ -17,61 +18,110 @@ typedef Future AngelConfigurer(Angel app); typedef T AngelDeserializer(x); /// Represents an Angel server that we are querying. -abstract class Angel { +abstract class Angel extends http.BaseClient { + /// A mutable member. When this is set, it holds a JSON Web Token + /// that is automatically attached to every request sent. + /// + /// This is designed with `package:angel_auth` in mind. String authToken; - String basePath; - Angel(String this.basePath); + /// The root URL at which the target server. + final Uri baseUrl; + + Angel(baseUrl) + : this.baseUrl = baseUrl is Uri ? baseUrl : Uri.parse(baseUrl.toString()); + + /// Prefer to use [baseUrl] instead. + @deprecated + String get basePath => baseUrl.toString(); /// Fired whenever a WebSocket is successfully authenticated. Stream get onAuthenticated; + /// Authenticates against the server. + /// + /// This is designed with `package:angel_auth` in mind. + /// + /// The [type] is appended to the [authEndpoint], ex. `local` becomes `/auth/local`. + /// + /// The given [credentials] are sent to server as-is; the request body is sent as JSON. Future authenticate( - {String type, + {@required String type, credentials, - String authEndpoint: '/auth', - String reviveEndpoint: '/auth/token'}); + String authEndpoint = '/auth', + @deprecated String reviveEndpoint = '/auth/token'}); + + /// Shorthand for authenticating via a JWT string. + Future reviveJwt(String token, + {String authEndpoint = '/auth'}) { + return authenticate( + type: 'token', + credentials: {'token': token}, + authEndpoint: authEndpoint); + } /// Opens the [url] in a new window, and returns a [Stream] that will fire a JWT on successful authentication. - Stream authenticateViaPopup(String url, {String eventName: 'token'}); + Stream authenticateViaPopup(String url, {String eventName = 'token'}); - Future close(); + /// Disposes of any outstanding resources. + Future close(); /// Applies an [AngelConfigurer] to this instance. - Future configure(AngelConfigurer configurer) async { + Future configure(AngelConfigurer configurer) async { await configurer(this); } /// Logs the current user out of the application. - Future logout(); + FutureOr logout(); + /// Creates a [Service] instance that queries a given path on the server. + /// + /// This expects that there is an Angel `Service` mounted on the server. + /// + /// In other words, all endpoints will return [Data], except for the root of + /// [path], which returns a [List]. + /// + /// You can pass a custom [deserializer], which is typically necessary in cases where + /// `dart:mirrors` does not exist. Service service(String path, - {Type type, AngelDeserializer deserializer}); + {@deprecated Type type, AngelDeserializer deserializer}); - Future delete(String url, {Map headers}); + @override + Future delete(url, {Map headers}); - Future get(String url, {Map headers}); + @override + Future get(url, {Map headers}); - Future head(String url, {Map headers}); + @override + Future head(url, {Map headers}); - Future patch(String url, {body, Map headers}); + @override + Future patch(url, + {body, Map headers, Encoding encoding}); - Future post(String url, {body, Map headers}); + @override + Future post(url, + {body, Map headers, Encoding encoding}); - Future put(String url, {body, Map headers}); + @override + Future put(url, + {body, Map headers, Encoding encoding}); } /// Represents the result of authentication with an Angel server. class AngelAuthResult { String _token; final Map data = {}; + + /// The JSON Web token that was sent with this response. String get token => _token; - AngelAuthResult({String token, Map data: const {}}) { + AngelAuthResult({String token, Map data = const {}}) { _token = token; this.data.addAll(data ?? {}); } + /// Attempts to deserialize a response from a [Map]. factory AngelAuthResult.fromMap(Map data) { final result = new AngelAuthResult(); @@ -81,12 +131,22 @@ class AngelAuthResult { if (data is Map) result.data.addAll((data['data'] as Map) ?? {}); + if (result.token == null) { + throw new FormatException( + 'The required "token" field was not present in the given data.'); + } else if (data['data'] is! Map) { + throw new FormatException( + 'The required "data" field in the given data was not a map; instead, it was ${data['data']}.'); + } + return result; } + /// Attempts to deserialize a response from a [String]. factory AngelAuthResult.fromJson(String s) => new AngelAuthResult.fromMap(json.decode(s) as Map); + /// Converts this instance into a JSON-friendly representation. Map toJson() { return {'token': token, 'data': data}; } @@ -225,7 +285,7 @@ class ServiceList extends DelegatingList { final List _subs = []; - ServiceList(this.service, {this.idField, Equality equality}) + ServiceList(this.service, {this.idField = 'id', Equality equality}) : super([]) { _equality = equality; _equality ??= new EqualityBy((map) { diff --git a/lib/auth_types.dart b/lib/auth_types.dart index f28a06af..5b98fe57 100644 --- a/lib/auth_types.dart +++ b/lib/auth_types.dart @@ -1,5 +1,4 @@ -const String local = 'local'; - -/// Use [local] instead. @deprecated -const String LOCAL = local; +library auth_types; + +const String local = 'local', token = 'token'; diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 4704085a..1b7f7f35 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -7,25 +7,17 @@ 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 'angel_client.dart'; -final RegExp straySlashes = new RegExp(r"(^/)|(/+$)"); const Map _readHeaders = const {'Accept': 'application/json'}; const Map _writeHeaders = const { 'Accept': 'application/json', 'Content-Type': 'application/json' }; -_buildQuery(Map params) { - if (params == null || params.isEmpty || params['query'] is! Map) return ""; - - List query = []; - - params['query'].forEach((k, v) { - query.add('$k=${Uri.encodeQueryComponent(v.toString())}'); - }); - - return '?' + query.join('&'); +Map _buildQuery(Map params) { + return params?.map((k, v) => new MapEntry(k, v.toString())); } bool _invalid(http.Response response) => @@ -36,7 +28,7 @@ bool _invalid(http.Response response) => AngelHttpException failure(http.Response response, {error, String message, StackTrace stack}) { try { - final v = json.decode(response.body); + var v = json.decode(response.body); if (v is Map && (v['is_error'] == true) || v['isError'] == true) { return new AngelHttpException.fromMap(v as Map); @@ -71,89 +63,48 @@ abstract class BaseAngelClient extends Angel { Future authenticate( {String type, credentials, - String authEndpoint: '/auth', - String reviveEndpoint: '/auth/token'}) async { - if (type == null) { - final url = '$basePath$reviveEndpoint'; - String token; + String authEndpoint = '/auth', + @deprecated String reviveEndpoint = '/auth/token'}) async { + type ??= 'token'; - if (credentials is String) - token = credentials; - else if (credentials is Map && credentials.containsKey('token')) - token = credentials['token'].toString(); + var segments = baseUrl.pathSegments + .followedBy(p.split(authEndpoint)) + .followedBy([type]); + var url = baseUrl.replace(path: p.joinAll(segments)); + http.Response response; - if (token == null) { - throw new ArgumentError( - 'If `type` is not set, a JWT is expected as the `credentials` argument.'); - } - - final response = await client.post(url, headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $token' - }); - - if (_invalid(response)) { - throw failure(response); - } - - try { - final v = json.decode(response.body); - - 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."); - } - - 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); - } + if (credentials != null) { + response = await post(url, + body: json.encode(credentials), headers: _writeHeaders); } else { - final url = '$basePath$authEndpoint/$type'; - http.Response response; + response = await post(url, headers: _writeHeaders); + } - if (credentials != null) { - response = await client.post(url, - body: json.encode(credentials), headers: _writeHeaders); - } else { - response = await client.post(url, headers: _writeHeaders); + if (_invalid(response)) { + throw failure(response); + } + + try { + var v = json.decode(response.body); + + 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."); } - if (_invalid(response)) { - throw failure(response); - } - - try { - final v = json.decode(response.body); - - 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."); - } - - 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); - } + 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); } } - Future close() async { + Future close() async { client.close(); _onAuthenticated.close(); Future.wait(_services.map((s) => s.close())).then((_) { @@ -161,10 +112,17 @@ abstract class BaseAngelClient extends Angel { }); } - Future logout() async { + Future logout() async { authToken = null; } + @override + Future send(http.BaseRequest request) async { + if (authToken?.isNotEmpty == true) + request.headers['authorization'] ??= 'Bearer $authToken'; + return client.send(request); + } + /// Sends a non-streaming [Request] and returns a non-streaming [Response]. Future sendUnstreamed( String method, url, Map headers, @@ -174,80 +132,77 @@ abstract class BaseAngelClient extends Angel { if (headers != null) request.headers.addAll(headers); - if (authToken?.isNotEmpty == true) - request.headers['Authorization'] = 'Bearer $authToken'; - if (encoding != null) request.encoding = encoding; if (body != null) { if (body is String) { request.body = body; - } else if (body is List) { - request.bodyBytes = new List.from(body); - } else if (body is Map) { - request.bodyFields = new Map.from(body); + } else if (body is List) { + request.bodyBytes = new List.from(body); + } else if (body is Map) { + request.bodyFields = new Map.from(body); } else { - throw new ArgumentError('Invalid request body "$body".'); + throw new ArgumentError.value(body, 'body', + 'must be a String, List, or Map.'); } } - return http.Response.fromStream(await client.send(request)); + return http.Response.fromStream(await send(request)); } @override Service service(String path, {Type type, AngelDeserializer deserializer}) { - String uri = path.toString().replaceAll(straySlashes, ""); - var s = new BaseAngelService(client, this, '$basePath/$uri', + var url = baseUrl.replace(path: p.join(baseUrl.path, path)); + var s = new BaseAngelService(client, this, url, deserializer: deserializer); _services.add(s); return s; } - String _join(url) { - final head = basePath.replaceAll(new RegExp(r'/+$'), ''); - final tail = url.replaceAll(straySlashes, ''); - return '$head/$tail'; + 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)); } @override - Future delete(String url, - {Map headers}) async { + Future delete(url, {Map headers}) async { return sendUnstreamed('DELETE', _join(url), headers); } @override - Future get(String url, {Map headers}) async { + Future get(url, {Map headers}) async { return sendUnstreamed('GET', _join(url), headers); } @override - Future head(String url, {Map headers}) async { + Future head(url, {Map headers}) async { return sendUnstreamed('HEAD', _join(url), headers); } @override - Future patch(String url, - {body, Map headers}) async { - return sendUnstreamed('PATCH', _join(url), headers, body); + Future patch(url, + {body, Map headers, Encoding encoding}) async { + return sendUnstreamed('PATCH', _join(url), headers, body, encoding); } @override - Future post(String url, - {body, Map headers}) async { - return sendUnstreamed('POST', _join(url), headers, body); + Future post(url, + {body, Map headers, Encoding encoding}) async { + return sendUnstreamed('POST', _join(url), headers, body, encoding); } @override - Future put(String url, - {body, Map headers}) async { - return sendUnstreamed('PUT', _join(url), headers, body); + Future put(url, + {body, Map headers, Encoding encoding}) async { + return sendUnstreamed('PUT', _join(url), headers, body, encoding); } } class BaseAngelService extends Service { @override final BaseAngelClient app; - final String basePath; + final Uri baseUrl; final http.BaseClient client; final AngelDeserializer deserializer; @@ -286,7 +241,12 @@ class BaseAngelService extends Service { _onRemoved.close(); } - BaseAngelService(this.client, this.app, this.basePath, {this.deserializer}); + 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(); Data deserialize(x) { return deserializer != null ? deserializer(x) : x as Data; @@ -306,8 +266,8 @@ class BaseAngelService extends Service { @override Future> index([Map params]) async { - final response = await app.sendUnstreamed( - 'GET', '$basePath${_buildQuery(params)}', _readHeaders); + var url = baseUrl.replace(queryParameters: _buildQuery(params)); + var response = await app.sendUnstreamed('GET', url, _readHeaders); try { if (_invalid(response)) { @@ -317,7 +277,7 @@ class BaseAngelService extends Service { throw failure(response); } - final v = json.decode(response.body) as List; + var v = json.decode(response.body) as List; var r = v.map(deserialize).toList(); _onIndexed.add(r); return r; @@ -333,8 +293,11 @@ class BaseAngelService extends Service { @override Future read(id, [Map params]) async { - final response = await app.sendUnstreamed( - 'GET', '$basePath/$id${_buildQuery(params)}', _readHeaders); + var url = baseUrl.replace( + path: p.join(baseUrl.path, id.toString()), + queryParameters: _buildQuery(params)); + + var response = await app.sendUnstreamed('GET', url, _readHeaders); try { if (_invalid(response)) { @@ -359,8 +322,9 @@ class BaseAngelService extends Service { @override Future create(data, [Map params]) async { - final response = await app.sendUnstreamed('POST', - '$basePath/${_buildQuery(params)}', _writeHeaders, makeBody(data)); + var url = baseUrl.replace(queryParameters: _buildQuery(params)); + var response = + await app.sendUnstreamed('POST', url, _writeHeaders, makeBody(data)); try { if (_invalid(response)) { @@ -385,8 +349,12 @@ class BaseAngelService extends Service { @override Future modify(id, data, [Map params]) async { - final response = await app.sendUnstreamed('PATCH', - '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); + var url = baseUrl.replace( + path: p.join(baseUrl.path, id.toString()), + queryParameters: _buildQuery(params)); + + var response = + await app.sendUnstreamed('PATCH', url, _writeHeaders, makeBody(data)); try { if (_invalid(response)) { @@ -411,8 +379,12 @@ class BaseAngelService extends Service { @override Future update(id, data, [Map params]) async { - final response = await app.sendUnstreamed('POST', - '$basePath/$id${_buildQuery(params)}', _writeHeaders, makeBody(data)); + var url = baseUrl.replace( + path: p.join(baseUrl.path, id.toString()), + queryParameters: _buildQuery(params)); + + var response = + await app.sendUnstreamed('POST', url, _writeHeaders, makeBody(data)); try { if (_invalid(response)) { @@ -437,8 +409,11 @@ class BaseAngelService extends Service { @override Future remove(id, [Map params]) async { - final response = await app.sendUnstreamed( - 'DELETE', '$basePath/$id${_buildQuery(params)}', _readHeaders); + var url = baseUrl.replace( + path: p.join(baseUrl.path, id.toString()), + queryParameters: _buildQuery(params)); + + var response = await app.sendUnstreamed('DELETE', url, _readHeaders); try { if (_invalid(response)) { diff --git a/lib/browser.dart b/lib/browser.dart index 54b67a80..5b25459a 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -15,42 +15,31 @@ export 'angel_client.dart'; class Rest extends BaseAngelClient { Rest(String basePath) : super(new http.BrowserClient(), basePath); - @override Future authenticate( {String type, credentials, - String authEndpoint: '/auth', - String reviveEndpoint: '/auth/token'}) async { - if (type == null) { + String authEndpoint = '/auth', + @deprecated String reviveEndpoint = '/auth/token'}) async { + if (type == null || type == 'token') { if (!window.localStorage.containsKey('token')) { throw new Exception( 'Cannot revive token from localStorage - there is none.'); } - try { - final result = await super.authenticate( - type: null, - credentials: {'token': json.decode(window.localStorage['token'])}, - reviveEndpoint: reviveEndpoint); - window.localStorage['token'] = json.encode(authToken = result.token); - window.localStorage['user'] = json.encode(result.data); - return result; - } catch (e, st) { - throw new AngelHttpException(e, - message: 'Failed to revive auth token.', stackTrace: st); - } - } else { - final result = await super.authenticate( - type: type, credentials: credentials, authEndpoint: authEndpoint); - window.localStorage['token'] = json.encode(authToken = result.token); - window.localStorage['user'] = json.encode(result.data); - return result; + var token = json.decode(window.localStorage['token']); + credentials ??= {'token': token}; } + + final result = await super.authenticate( + type: type, credentials: credentials, authEndpoint: authEndpoint); + window.localStorage['token'] = json.encode(authToken = result.token); + window.localStorage['user'] = json.encode(result.data); + return result; } @override Stream authenticateViaPopup(String url, - {String eventName: 'token', String errorMessage}) { + {String eventName = 'token', String errorMessage}) { var ctrl = new StreamController(); var wnd = window.open(url, 'angel_client_auth_popup'); diff --git a/lib/flutter.dart b/lib/flutter.dart index 83bfec5e..5e908fb5 100644 --- a/lib/flutter.dart +++ b/lib/flutter.dart @@ -11,8 +11,9 @@ class Rest extends BaseAngelClient { Rest(String basePath) : super(new http.Client() as http.BaseClient, basePath); @override - Stream authenticateViaPopup(String url, {String eventName: 'token'}) { + Stream authenticateViaPopup(String url, + {String eventName = 'token'}) { throw new UnimplementedError( - 'Opening popup windows is not supported in the `dart:io` client.'); + 'Opening popup windows is not supported in the `flutter` client.'); } } diff --git a/lib/io.dart b/lib/io.dart index cb50df37..85ad0822 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -4,6 +4,7 @@ library angel_client.cli; import 'dart:async'; import 'package:http/http.dart' as http; import 'package:json_god/json_god.dart' as god; +import 'package:path/path.dart' as p; import 'angel_client.dart'; import 'base_angel_client.dart'; export 'angel_client.dart'; @@ -17,14 +18,15 @@ class Rest extends BaseAngelClient { @override Service service(String path, {Type type, AngelDeserializer deserializer}) { - String uri = path.replaceAll(straySlashes, ""); - var s = new RestService(client, this, "$basePath/$uri", type); + var url = baseUrl.replace(path: p.join(baseUrl.path, path)); + var s = new RestService(client, this, url, type); _services.add(s); return s; } @override - Stream authenticateViaPopup(String url, {String eventName: 'token'}) { + Stream authenticateViaPopup(String url, + {String eventName = 'token'}) { throw new UnimplementedError( 'Opening popup windows is not supported in the `dart:io` client.'); } @@ -41,8 +43,7 @@ class Rest extends BaseAngelClient { class RestService extends BaseAngelService { final Type type; - RestService( - http.BaseClient client, BaseAngelClient app, String url, this.type) + RestService(http.BaseClient client, BaseAngelClient app, url, this.type) : super(client, app, url); @override diff --git a/pubspec.yaml b/pubspec.yaml index 5e8aa7b1..60056482 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 2.0.0-alpha.2 +version: 2.0.0 description: Client library for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_client @@ -8,13 +8,14 @@ environment: dependencies: angel_http_exception: ^1.0.0 collection: ^1.0.0 - http: ">=0.11.0 <2.0.0" + http: ^0.12.0 json_god: ">=2.0.0-beta <3.0.0" - merge_map: ^1.0.0 + path: ^1.0.0 dev_dependencies: angel_framework: ^2.0.0-alpha angel_model: ^1.0.0 - build_runner: ^0.10.0 - build_web_compilers: ^0.4.0 + build_runner: ^1.0.0 + build_web_compilers: ^1.0.0 mock_request: ^1.0.0 + pedantic: ^1.0.0 test: ^1.0.0 diff --git a/test/all_test.dart b/test/all_test.dart index 4816d38e..f80821bb 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -33,7 +33,7 @@ main() { expect(app.client.spec.method, 'POST'); expect(app.client.spec.headers['content-type'], startsWith('application/json')); - expect(app.client.spec.path, '/api/todos/'); + expect(app.client.spec.path, '/api/todos'); expect(await read(app.client.spec.request.finalize()), '{}'); }); @@ -63,10 +63,6 @@ main() { }); group('authentication', () { - test('no type, no token throws', () async { - expect(app.authenticate, throwsArgumentError); - }); - test('no type defaults to token', () async { await app.authenticate(credentials: ''); expect(app.client.spec.path, '/auth/token'); @@ -77,11 +73,6 @@ main() { expect(app.client.spec.path, '/auth/local'); }); - test('token sends headers', () async { - await app.authenticate(credentials: ''); - expect(app.client.spec.headers['authorization'], 'Bearer '); - }); - test('credentials send right body', () async { await app .authenticate(type: 'local', credentials: {'username': 'password'}); diff --git a/test/common.dart b/test/common.dart index bc54f5cf..8d7e2c90 100644 --- a/test/common.dart +++ b/test/common.dart @@ -15,7 +15,7 @@ class MockAngel extends BaseAngelClient { MockAngel() : super(null, 'http://localhost:3000'); @override - authenticateViaPopup(String url, {String eventName: 'token'}) { + authenticateViaPopup(String url, {String eventName = 'token'}) { throw new UnsupportedError('Nope'); } } diff --git a/web/main.dart b/web/main.dart index 4cd7910d..9e12ec08 100644 --- a/web/main.dart +++ b/web/main.dart @@ -4,5 +4,5 @@ import 'package:angel_client/browser.dart'; /// Dummy app to ensure client works with DDC. main() { var app = new Rest(window.location.origin); - window.alert(app.basePath); + window.alert(app.baseUrl.toString()); } From 4594f39d7e0b578438b56912888a3c91ffa74b50 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 5 Jan 2019 21:10:21 -0500 Subject: [PATCH 64/69] pubspec description --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 60056482..9190626a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_client version: 2.0.0 -description: Client library for the Angel framework. +description: Support for querying Angel servers in the browser, Flutter, and command-line. author: Tobe O homepage: https://github.com/angel-dart/angel_client environment: From fdb37660b294b5d04b32b92b4dbabecf5c6eef07 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 5 Jan 2019 21:11:06 -0500 Subject: [PATCH 65/69] Depend on pkg:meta --- pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index 9190626a..9f131968 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,10 +10,12 @@ dependencies: collection: ^1.0.0 http: ^0.12.0 json_god: ">=2.0.0-beta <3.0.0" + meta: ^1.0.0 path: ^1.0.0 dev_dependencies: angel_framework: ^2.0.0-alpha angel_model: ^1.0.0 + async: ^2.0.0 build_runner: ^1.0.0 build_web_compilers: ^1.0.0 mock_request: ^1.0.0 From 7ead5054d9f40fd0472064b69c0746d3a8d9083d Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 5 Jan 2019 21:33:10 -0500 Subject: [PATCH 66/69] 2.0.1 --- CHANGELOG.md | 3 +++ lib/base_angel_client.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb5ffd3..63a7f52b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.0.1 +* Change `BaseAngelClient` constructor to accept `dynamic` instead of `String` for `baseUrl. + # 2.0.0 * Deprecate `basePath` in favor of `baseUrl`. * `Angel` now extends `http.Client`. diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 1b7f7f35..87fd305e 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -57,7 +57,7 @@ abstract class BaseAngelClient extends Angel { @override Stream get onAuthenticated => _onAuthenticated.stream; - BaseAngelClient(this.client, String basePath) : super(basePath); + BaseAngelClient(this.client, baseUrl) : super(baseUrl); @override Future authenticate( diff --git a/pubspec.yaml b/pubspec.yaml index 9f131968..2577954c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 2.0.0 +version: 2.0.1 description: Support for querying Angel servers in the browser, Flutter, and command-line. author: Tobe O homepage: https://github.com/angel-dart/angel_client From 6c9379698a55cf63c21899ca086007d8d264fd98 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 20 Apr 2019 11:21:15 -0400 Subject: [PATCH 67/69] 2.0.2 --- CHANGELOG.md | 3 +++ analysis_options.yaml | 4 ++-- lib/angel_client.dart | 2 +- lib/base_angel_client.dart | 18 +++++++++--------- lib/io.dart | 4 ++-- pubspec.yaml | 2 +- test/list_test.dart | 7 ++++--- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63a7f52b..f4c29fee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.0.2 +* `_join` previously discarded quer parameters, etc. + # 2.0.1 * Change `BaseAngelClient` constructor to accept `dynamic` instead of `String` for `baseUrl. diff --git a/analysis_options.yaml b/analysis_options.yaml index a146a9e6..64bd1e8f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,5 +2,5 @@ include: package:pedantic/analysis_options.yaml analyzer: strong-mode: implicit-casts: false - errors: - unawaited_futures: ignore \ No newline at end of file + # errors: + # unawaited_futures: ignore \ No newline at end of file diff --git a/lib/angel_client.dart b/lib/angel_client.dart index f681786b..72b2c223 100644 --- a/lib/angel_client.dart +++ b/lib/angel_client.dart @@ -342,6 +342,6 @@ class ServiceList extends DelegatingList { Stream> get onChange => _onChange.stream; Future close() async { - _onChange.close(); + await _onChange.close(); } } diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index 87fd305e..f11eb3aa 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -106,8 +106,8 @@ abstract class BaseAngelClient extends Angel { Future close() async { client.close(); - _onAuthenticated.close(); - Future.wait(_services.map((s) => s.close())).then((_) { + await _onAuthenticated.close(); + await Future.wait(_services.map((s) => s.close())).then((_) { _services.clear(); }); } @@ -162,7 +162,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 baseUrl.replace(path: p.join(baseUrl.path, u.path)); + return u.replace(path: p.join(baseUrl.path, u.path)); } @override @@ -233,12 +233,12 @@ class BaseAngelService extends Service { @override Future close() async { - _onIndexed.close(); - _onRead.close(); - _onCreated.close(); - _onModified.close(); - _onUpdated.close(); - _onRemoved.close(); + await _onIndexed.close(); + await _onRead.close(); + await _onCreated.close(); + await _onModified.close(); + await _onUpdated.close(); + await _onRemoved.close(); } BaseAngelService(this.client, this.app, baseUrl, {this.deserializer}) diff --git a/lib/io.dart b/lib/io.dart index 85ad0822..c94a4ee2 100644 --- a/lib/io.dart +++ b/lib/io.dart @@ -32,8 +32,8 @@ class Rest extends BaseAngelClient { } Future close() async { - super.close(); - Future.wait(_services.map((s) => s.close())).then((_) { + await super.close(); + await Future.wait(_services.map((s) => s.close())).then((_) { _services.clear(); }); } diff --git a/pubspec.yaml b/pubspec.yaml index 2577954c..6888dffb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_client -version: 2.0.1 +version: 2.0.2 description: Support for querying Angel servers in the browser, Flutter, and command-line. author: Tobe O homepage: https://github.com/angel-dart/angel_client diff --git a/test/list_test.dart b/test/list_test.dart index 29beb3bf..50fd122d 100644 --- a/test/list_test.dart +++ b/test/list_test.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:angel_client/io.dart' as c; import 'package:angel_framework/angel_framework.dart' as s; import 'package:angel_framework/http.dart' as s; +import 'package:pedantic/pedantic.dart'; import 'package:test/test.dart'; main() { @@ -31,7 +32,7 @@ main() { }); test('listens on create', () async { - list.service.create({'foo': 'bar'}); + unawaited(list.service.create({'foo': 'bar'})); await list.onChange.first; expect(list, [ {'foo': 'bar'} @@ -39,7 +40,7 @@ main() { }); test('listens on modify', () async { - list.service.create({'id': 1, 'foo': 'bar'}); + unawaited(list.service.create({'id': 1, 'foo': 'bar'})); await queue.next; await list.service.update(1, {'id': 1, 'bar': 'baz'}); @@ -50,7 +51,7 @@ main() { }); test('listens on remove', () async { - list.service.create({'id': '1', 'foo': 'bar'}); + unawaited(list.service.create({'id': '1', 'foo': 'bar'})); await queue.next; await list.service.remove('1'); From 47ccb07218de9d9390f20104861f26dc5f4cd2e4 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 20 Apr 2019 11:23:06 -0400 Subject: [PATCH 68/69] Allow any Map as body, not just Map --- CHANGELOG.md | 1 + lib/base_angel_client.dart | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c29fee..1d0f2aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 2.0.2 * `_join` previously discarded quer parameters, etc. +* Allow any `Map` as body, not just `Map`. # 2.0.1 * Change `BaseAngelClient` constructor to accept `dynamic` instead of `String` for `baseUrl. diff --git a/lib/base_angel_client.dart b/lib/base_angel_client.dart index f11eb3aa..7912243b 100644 --- a/lib/base_angel_client.dart +++ b/lib/base_angel_client.dart @@ -138,8 +138,9 @@ abstract class BaseAngelClient extends Angel { request.body = body; } else if (body is List) { request.bodyBytes = new List.from(body); - } else if (body is Map) { - request.bodyFields = new Map.from(body); + } else if (body is Map) { + request.bodyFields = + body.map((k, v) => MapEntry(k, v is String ? v : v.toString())); } else { throw new ArgumentError.value(body, 'body', 'must be a String, List, or Map.'); From 180edbc46a556f6d572c3b4ade4b396a31a1bc42 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 20 Apr 2019 11:25:13 -0400 Subject: [PATCH 69/69] Fix broken tests --- test/list_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/list_test.dart b/test/list_test.dart index 50fd122d..ba632a9e 100644 --- a/test/list_test.dart +++ b/test/list_test.dart @@ -26,9 +26,9 @@ main() { tearDown(() async { await server.close(force: true); - await list.close(); - await list.service.close(); - await app.close(); + unawaited(list.close()); + unawaited(list.service.close()); + unawaited(app.close()); }); test('listens on create', () async {