Skip to content

Commit 72f843b

Browse files
committed
finish rest client
1 parent 52395d1 commit 72f843b

15 files changed

+393
-469
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,6 @@ build/
7373
!**/ios/**/default.pbxuser
7474
!**/ios/**/default.perspectivev3
7575
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
76+
77+
# sembast db
78+
flutter_feathersjs.db

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# The MIT License (MIT)
22

3-
Copyright (c) 2019 Justin Y. Dah-kenangnon <dah.kenangnon@gmail.com>
3+
Copyright (c) 2019 Justin Dah-kenangnon <dah.kenangnon@gmail.com>
44

55
> Permission is hereby granted, free of charge, to any person obtaining a copy
66
> of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1-
# feathersjs
1+
# flutter_feathersjs
22

3-
Under dev, please don't use it now.
3+
Communicate with you feathers js server from flutter.
4+
5+
## THIS IS UNDER DEV, DON'T USE IT AT THIS MOMENT IN PROD
6+
7+
## TODO
8+
9+
- [x] Write auth for rest
10+
- [x] Write reAuth for rest
11+
- [x] Write rest client method (feathers js service methods)
12+
- [ ] Handle formData on rest
13+
14+
- [ ] Write auth for socket
15+
- [ ] Write reAuth for socket
16+
- [ ] Write socket client method (feathers js service methods)
17+
- [ ] Handle formData on socket
18+
- [ ] Write an unique flutter_feathersjs plugin which won't mater if you calling service via rest or sock
19+
- [ ] Write doc

lib/feathersjs.dart

Lines changed: 0 additions & 49 deletions
This file was deleted.

lib/flutter_feathersjs.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
library flutter_feathersjs;
2+
3+
import 'package:flutter_feathersjs/src/rest_client.dart';
4+
import 'package:flutter_feathersjs/src/socketio_client.dart';
5+
import 'package:meta/meta.dart';
6+
7+
class FlutterFeathersjs {
8+
/////////////////////////////////////////////////////////////////////
9+
RestClient rest;
10+
SocketioClient scketio;
11+
12+
////////////////////////////////////////////////////////////////////////
13+
14+
//Using singleton
15+
static final FlutterFeathersjs _flutterFeathersjs =
16+
FlutterFeathersjs._internal();
17+
factory FlutterFeathersjs() {
18+
return _flutterFeathersjs;
19+
}
20+
FlutterFeathersjs._internal();
21+
22+
//intialize both rest and scoketio client, base url and headers.
23+
config({@required String baseUrl, Map<String, dynamic> extraHeaders}) async {
24+
rest = new RestClient(baseUrl: baseUrl, extraHeaders: extraHeaders);
25+
26+
//Auth only by rest, so we must call auth or reAuth from rest before use scketio
27+
var oldToken = await this.rest.utils.getRestAccessToken();
28+
Map<String, dynamic> scketIOExtraHeaders = {
29+
"Authorization": "Bearer $oldToken"
30+
};
31+
scketio =
32+
new SocketioClient(baseUrl: baseUrl, extraHeaders: scketIOExtraHeaders);
33+
}
34+
35+
/// Authenticate client
36+
Future<dynamic> authenticate(
37+
{@required String email,
38+
@required String password,
39+
String client = "rest"}) async {
40+
return await rest.authenticate(email: email, password: password);
41+
}
42+
}

lib/src/event.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
abstract class FlutterFeathersjsEvents {}
2+
3+
class Connected extends FlutterFeathersjsEvents {}
4+
5+
class Disconnected extends FlutterFeathersjsEvents {}

lib/src/featherjs_client_base.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
//Base classe for both rest and socketio
22

3-
class FeathersJsClient {}
3+
import './utils.dart';
4+
5+
class FlutterFeathersjs {
6+
Utils utils = new Utils();
7+
}

lib/src/rest_client.dart

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,149 @@
11
import 'dart:async';
22

33
import 'package:dio/dio.dart';
4-
import 'package:feathersjs/src/featherjs_client_base.dart';
4+
import 'package:flutter_feathersjs/src/featherjs_client_base.dart';
55
import 'package:meta/meta.dart';
66

7-
class RestClient extends FeathersJsClient {
8-
/////////////////////////////////////////////////////////////////////
7+
/// Feathers Js rest client
8+
class RestClient extends FlutterFeathersjs {
9+
///Dio as http client
910
Dio dio;
10-
////////////////////////////////////////////////////////////////////////
1111

1212
RestClient({@required String baseUrl, Map<String, dynamic> extraHeaders}) {
1313
//Setup Http client
1414
dio = Dio(BaseOptions(
1515
baseUrl: baseUrl,
1616
headers: extraHeaders,
1717
));
18+
19+
dio.interceptors
20+
.add(InterceptorsWrapper(onRequest: (RequestOptions options) async {
21+
// Do something before request is sent
22+
//Adding stored token early with sembast
23+
var oldToken = await this.utils.getRestAccessToken();
24+
dio.options.headers["Authorization"] = "Bearer $oldToken";
25+
return options; //continue
26+
// If you want to resolve the request with some custom data,
27+
// you can return a `Response` object or return `dio.resolve(data)`.
28+
// If you want to reject the request with a error message,
29+
// you can return a `DioError` object or return `dio.reject(errMsg)`
30+
}, onResponse: (Response response) async {
31+
// Do something with response data
32+
return response; // continue
33+
}, onError: (DioError e) async {
34+
// Do something with response error
35+
36+
if (e.response != null) {
37+
// print(e.response.data);
38+
// print(e.response.headers);
39+
// print(e.response.request);
40+
//Only send the error message from feathers js server not for Dio
41+
return e.response.data; //continue
42+
} else {
43+
// Something happened in setting up or sending the request that triggered an Error
44+
// print(e.request);
45+
// print(e.message);
46+
//By returning null, it means that error is from client
47+
return null;
48+
}
49+
}));
50+
}
51+
52+
Future<dynamic> reAuthenticate({String serviceName = "users"}) async {
53+
//AsyncTask manager
54+
Completer asyncTask = Completer<dynamic>();
55+
Map<String, dynamic> authResponse = {
56+
"error": true,
57+
"error_zone": "UNKNOWN",
58+
"message": "An error occured"
59+
};
60+
61+
//Getting the early sotored rest access token and send the request by using it
62+
var oldToken = await this.utils.getRestAccessToken();
63+
64+
//If an oldToken exist really
65+
if (oldToken != null) {
66+
dio.options.headers["Authorization"] = "Bearer $oldToken";
67+
var response =
68+
await find(serviceName: "$serviceName", query: {"\$limit": 1});
69+
70+
if (response.statusCode == 401) {
71+
print("jwt expired or jwt malformed");
72+
authResponse["error"] = true;
73+
authResponse["message"] = "jwt expired";
74+
authResponse["error_zone"] = "JWT_EXPIRED_ERROR";
75+
} else if (response.statusCode == 200) {
76+
print("Jwt still validated");
77+
authResponse["error"] = false;
78+
authResponse["message"] = "Jwt still validated";
79+
authResponse["error_zone"] = "NO_ERROR";
80+
} else {
81+
print("Unknown error");
82+
authResponse["error"] = true;
83+
authResponse["message"] = "Unknown error";
84+
authResponse["error_zone"] = "UNKNOWN_ERROR";
85+
}
86+
} else {
87+
print("No old token found. Must reAuth user");
88+
authResponse["error"] = true;
89+
authResponse["message"] = "No old token found. Must reAuth user";
90+
authResponse["error_zone"] = "JWT_NOT_FOUND";
91+
}
92+
asyncTask.complete(authResponse);
93+
return asyncTask.future;
1894
}
1995

20-
Future<bool> authenticate(
96+
Future<dynamic> authenticate(
2197
{strategy = "local", String email, String password}) async {
22-
Completer asyncTask = Completer<bool>();
98+
Completer asyncTask = Completer<dynamic>();
99+
100+
Map<String, dynamic> authResponse = {
101+
"error": true,
102+
"error_zone": "UNKNOWN",
103+
"message": "An error occured"
104+
};
105+
23106
try {
24107
//Making http request to get auth token
25108
var response = await dio.post("/authentication",
26109
data: {"strategy": strategy, "email": email, "password": password});
27-
var token = response.data['accessToken'];
28-
dio.options.headers["Authorization"] = "Bearer $token";
29-
asyncTask.complete(true);
110+
//Check is auth is successfully before storing token
111+
if (response.data["code"] != null && response.data["code"] == 401) {
112+
//This is useful to display to end user why auth failed
113+
//With 401: it will be either Invalid credentials or strategy error
114+
if (response.data["message"] == "Invalid login") {
115+
authResponse["error_zone"] = "INVALID_CREDENTIALS";
116+
} else {
117+
authResponse["error_zone"] = "INVALID_STRATEGY";
118+
}
119+
authResponse["error"] = true;
120+
authResponse["message"] = response.data["message"];
121+
} else if (response.data['accessToken'] != null) {
122+
authResponse["error"] = false;
123+
authResponse["message"] = response.data["user"];
124+
authResponse["error_zone"] = "NO_ERROR";
125+
126+
//Store the authenticated user
127+
await this
128+
.utils
129+
.setAuthenticatedFeathersUser(user: response.data['user']);
130+
//Storing the token
131+
await this
132+
.utils
133+
.setRestAccessToken(token: response.data['accessToken']);
134+
} else {
135+
//Unknown error
136+
authResponse["error"] = true;
137+
authResponse["message"] = "Unknown error occured";
138+
}
30139
} catch (e) {
31-
asyncTask.complete(false);
140+
//Error caught by Dio
141+
authResponse["error"] = true;
142+
authResponse["message"] = e;
143+
authResponse["error_zone"] = "DIO_ERROR";
32144
}
145+
//Send response
146+
asyncTask.complete(authResponse);
33147
return asyncTask.future;
34148
}
35149

0 commit comments

Comments
 (0)