From 5ffb3cde678c2fd97cbd20bacdedb6c2f8ca431a Mon Sep 17 00:00:00 2001 From: Justin Dah-kenangnon Date: Wed, 25 Nov 2020 22:45:51 +0100 Subject: [PATCH] Add event listen, document code, support other auth strategy, ... --- CHANGELOG.md | 13 +- LICENSE | 2 +- README.md | 33 +++-- example/pubspec.lock | 15 +- example/pubspec.yaml | 3 +- lib/flutter_feathersjs.dart | 59 +++++++- lib/src/constants.dart | 9 +- lib/src/rest_client.dart | 219 ++++++++++++++++++++++++---- lib/src/scketio_client.dart | 248 ++++++++++++++++++++++++++------ lib/src/utils.dart | 13 +- pubspec.yaml | 2 +- test/feathersjs_test.dart | 276 ++++++++++++++++++------------------ test/fixtures.dart | 9 +- 13 files changed, 642 insertions(+), 259 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d817888..ee40c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,18 @@ -## 0.0.7 +## 0.1.0 -## 0.0.1+1 + - Improvment: Documentation of the code + - Breaking: on both rest and socketio, you get exactly what feathers js send, we don't support any serializer or deserializer. So how previous version handle data is breaken + - Feature: Add event listen but not fully tested + - Authentication don't matter if you auth user with email/password. You can use email/password, phone/password, userName/password + - Feature: support now phone, email, etc, with password authentication - - remove unsed comment -## 0.0.1 +## 0.0.7 + - remove unused comment - Add formData handling - Update readme + ## 0.0.6-dev - Fix diff --git a/LICENSE b/LICENSE index b3a4be3..54fa975 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ # The MIT License (MIT) -Copyright (c) 2019 Justin Dah-kenangnon +Copyright (c) 2020-present Justin Dah-kenangnon > Permission is hereby granted, free of charge, to any person obtaining a copy > of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 19d9037..d31e958 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# flutter_feathersjs :bird: +# :bird: flutter_feathersjs :bird: -Communicate with your feathers js server from flutter. -*__FormData support out the box, auth, reAuth, socketio send event, rest ...__ But event listen via socketio not yet implemented* +Communicate with your feathers js (https://feathersjs.com/) server from flutter. +`Infos: Feathers js is a node framework for real-time applications and REST APIs.` + + +*__FormData support out the box, auth, reAuth, socketio send event, rest ...__ ## Import it @@ -22,34 +25,44 @@ FlutterFeathersjs flutterFeathersjs = FlutterFeathersjs() ## Authentication with either with rest or socketio ```dart - // Auth with rest client + // Auth with rest client with email/password [Default strategy is email/password] var authResponse = await flutterFeathersjs.rest - .authenticate(email: user["email"], password: user["password"]); + .authenticate(userName: user["email"], password: user["password"]); + + // Auth with rest client with phone/password + var authResponse = await flutterFeathersjs.rest.authenticate( + strategy: "phone", + userNameFieldName: "tel", + userName: user["tel"], + password: user["password"]); //Second time, application will restart, just: var reAuthResponse = await flutterFeathersjs.rest.reAuthenticate(); // Or With socketio // Note: This must be call after rest auth success + // Not recommanded to use this directly var authResponse = await flutterFeathersjs.scketio.authWithJWT(); ``` -## Simplify yor life, Auth both client one time +## Auth both client one time ```dart // Auth first time with credentials -var authResponse = await flutterFeathersjs.authenticate(email: null, password: null); + var authResponse = await flutterFeathersjs.authenticate( + strategy: "phone", + userNameFieldName: "tel", + userName: user["tel"], + password: user["password"]); // on App restart, don't disturbe user, just reAuth with with accessToken, early store by FlutterFeathersjs var reAuthResponse = await flutterFeathersjs.reAuthenticate() ``` -## Important +## Contribution -- Listen to event on socketio is not yet implemented -- Documentation is coming - Contact-me for improvment or contribution purpose - Example is coming \ No newline at end of file diff --git a/example/pubspec.lock b/example/pubspec.lock index edfd207..99957b5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -57,6 +57,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.10" + event_bus: + dependency: transitive + description: + name: event_bus + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" fake_async: dependency: transitive description: @@ -86,10 +93,10 @@ packages: flutter_feathersjs: dependency: "direct main" description: - name: flutter_feathersjs - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.6-dev" + path: ".." + relative: true + source: path + version: "0.1.0" flutter_test: dependency: "direct dev" description: flutter diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 12ced1f..efb8af2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -10,7 +10,8 @@ environment: dependencies: flutter: sdk: flutter - flutter_feathersjs: ^0.0.7 + flutter_feathersjs: + path: ../ cupertino_icons: ^1.0.0 dev_dependencies: diff --git a/lib/flutter_feathersjs.dart b/lib/flutter_feathersjs.dart index f48693a..7071a7f 100644 --- a/lib/flutter_feathersjs.dart +++ b/lib/flutter_feathersjs.dart @@ -1,17 +1,44 @@ +/// Communicate with your feathers js (https://feathersjs.com/) server from flutter app. library flutter_feathersjs; import 'package:flutter_feathersjs/src/constants.dart'; import 'package:flutter_feathersjs/src/rest_client.dart'; import 'package:flutter_feathersjs/src/scketio_client.dart'; import 'package:meta/meta.dart'; +//import 'package:flutter/foundation.dart' as Foundation; -/// FlutterFeatherJs allow you to communicate with your feathers js server +/// +///FlutterFeatherJs allow you to communicate with your feathers js server +/// +///_______________________________________________ +///_______________________________________________ +/// +/// +/// `GENERAL NOTE 1`: Serialization and Deserialization are not supported. +/// +/// You get exactly what feathers server send +/// +/// `GENERAL NOTE 2`: To send file, use rest client, socketio client cannot upload file +/// +/// Authentification is processed by: +/// +/// // Global auth (Rest and Socketio) +/// var rep = await flutterFeathersjs +/// .authenticate(userName: user["email"], password: user["password"]); +/// +/// //Then use this one to reUse access token as it still valided +/// var reAuthResp = await flutterFeathersjs.reAuthenticate(); +/// +/// class FlutterFeathersjs { //RestClient RestClient rest; //SocketioClient SocketioClient scketio; + // For debug purpose + bool dev = false; + ///Using singleton static final FlutterFeathersjs _flutterFeathersjs = FlutterFeathersjs._internal(); @@ -22,13 +49,32 @@ class FlutterFeathersjs { ///Intialize both rest and scoketio client init({@required String baseUrl, Map extraHeaders}) { + // if (Foundation.kReleaseMode) { + // // W're on release mode + // dev = false; + // } else { + // // W're not on release mode + // dev = true; + // } + rest = new RestClient()..init(baseUrl: baseUrl, extraHeaders: extraHeaders); + scketio = new SocketioClient()..init(baseUrl: baseUrl); } /// Authenticate rest and scketio clients so you can use both of them + /// + ///___________________________________________________________________ + /// @params `username` can be : email, phone, etc; + /// + /// But ensure that `userNameFieldName` is correct with your chosed `strategy` + /// + /// By default this will be `email`and the strategy `local` Future> authenticate( - {@required String email, @required String password}) async { + {String strategy = "local", + @required String userName, + @required String password, + String userNameFieldName = "email"}) async { //Hold global auth infos Map authResponse = { "error": true, @@ -39,8 +85,11 @@ class FlutterFeathersjs { }; //Auth with rest to refresh or create accessToken - Map restAuthResponse = - await rest.authenticate(email: email, password: password); + Map restAuthResponse = await rest.authenticate( + strategy: strategy, + userName: userName, + userNameFieldName: userNameFieldName, + password: password); //Then auth with jwt socketio Map socketioAuthResponse = await scketio.authWithJWT(); @@ -55,6 +104,8 @@ class FlutterFeathersjs { } /// Authenticate rest and scketio clients so you can use both of them + /// + ///___________________________________________________________________ Future> reAuthenticate() async { //Hold global auth infos Map authResponse = { diff --git a/lib/src/constants.dart b/lib/src/constants.dart index d768b84..5741477 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -14,16 +14,9 @@ class Constants { static const String BOTH_CLIENT_AUTHED = "BOTH_CLIENT_AUTHED"; static const String ONE_OR_BOTH_CLIENT_NOT_AUTHED = "ONE_OR_BOTH_CLIENT_NOT_AUTHED"; + static const String FEATHERSJS_ACCESS_TOKEN = "FEATHERSJS_ACCESS_TOKEN"; } -class Removed {} - -class Patched {} - -class Created {} - -class Updated {} - class Connected {} class DisConnected {} diff --git a/lib/src/rest_client.dart b/lib/src/rest_client.dart index cc5a9f7..70bb847 100644 --- a/lib/src/rest_client.dart +++ b/lib/src/rest_client.dart @@ -6,19 +6,31 @@ import 'package:flutter_feathersjs/src/featherjs_client_base.dart'; import 'package:flutter_feathersjs/src/utils.dart'; import 'package:meta/meta.dart'; -/// Feathers Js rest client for rest api call +/// +///Feathers Js rest client for rest api call +/// +///_______________________________________________ +///_______________________________________________ +/// +/// +/// `GENERAL NOTE 1`: Serialization and Deserialization are not supported. +/// +/// You get exactly what feathers server send +/// class RestClient extends FlutterFeathersjs { ///Dio as http client Dio dio; Utils utils; + bool dev = true; - //Using singleton to ensure we use the same instance of it + //Using singleton to ensure we use the same instance of it accross our app static final RestClient _restClient = RestClient._internal(); factory RestClient() { return _restClient; } RestClient._internal(); + /// Initialize FlutterFeathersJs with the server baseUrl init({@required String baseUrl, Map extraHeaders}) { utils = new Utils(); @@ -31,7 +43,7 @@ class RestClient extends FlutterFeathersjs { dio.interceptors .add(InterceptorsWrapper(onRequest: (RequestOptions options) async { // Do something before request is sent - //Adding stored token early with sembast + //Adding stored token early with SharedPreferences var oldToken = await utils.getAccessToken(); dio.options.headers["Authorization"] = "Bearer $oldToken"; return options; //continue @@ -64,7 +76,9 @@ class RestClient extends FlutterFeathersjs { })); } - //Authenticate with jwt + /// `Authenticate with JWT` + /// + /// The @params serviceName is use to test if the past token still validated Future reAuthenticate({String serviceName = "users"}) async { //AsyncTask manager Completer asyncTask = Completer(); @@ -74,7 +88,7 @@ class RestClient extends FlutterFeathersjs { "message": "An error occured" }; - //Getting the early sotored rest access token and send the request by using it + //Getting the early stored rest access token and send the request by using it var oldToken = await utils.getAccessToken(); ///If an oldToken exist really, try to chect it is still valided @@ -121,9 +135,18 @@ class RestClient extends FlutterFeathersjs { return asyncTask.future; } - //Authenticate with email & password + /// Authenticate with username & password + /// + /// @params `username` can be : email, phone, etc; + /// + /// But ensure that `userNameFieldName` is correct with your chosed `strategy` + /// + /// By default this will be `email`and the strategy `local` Future authenticate( - {strategy = "local", String email, String password}) async { + {strategy = "local", + String userName, + String password, + String userNameFieldName = "email"}) async { Completer asyncTask = Completer(); Map authResponse = { @@ -134,8 +157,11 @@ class RestClient extends FlutterFeathersjs { try { //Making http request to get auth token - var response = await dio.post("/authentication", - data: {"strategy": strategy, "email": email, "password": password}); + var response = await dio.post("/authentication", data: { + "strategy": strategy, + "$userNameFieldName": userName, + "password": password + }); //Check is auth is successfully before storing token if (response.data["code"] != null && response.data["code"] == 401) { //This is useful to display to end user why auth failed @@ -170,8 +196,10 @@ class RestClient extends FlutterFeathersjs { return asyncTask.future; } - /// GET /serviceName - /// Retrieves a list of all matching resources from the service + /// `GET /serviceName` + /// + /// Retrieves a list of all matching the `query` resources from the service + /// Future> find( {String serviceName, Map query}) async { var response; @@ -184,8 +212,10 @@ class RestClient extends FlutterFeathersjs { return response; } - /// GET /serviceName/_id - /// Retrieve a single resource from the service. + /// `GET /serviceName/_id` + /// + /// Retrieve a single resource from the service with an `_id` + /// Future> get({String serviceName, objectId}) async { var response; try { @@ -197,14 +227,43 @@ class RestClient extends FlutterFeathersjs { return response; } - /// POST /serviceName + /// `POST /serviceName` + /// /// Create a new resource with data. - /// @var fieldsMap: other field non file - ///@var hasSingleFile: true for signle file , false otherwise - ///@ fileFieldName: the file | files field which must be send to the server - ///@var files: a List map of {"filePath": the file path, "fileName": the file ame} - ///if hasSingleFile is true, just the file first entry of the list otherwise looping through the + /// + /// The below is important if you have file to upload [containsFile == true] + /// + /// + ///@var `hasSingleFile`: true for signle file , false otherwise + /// + ///@ `fileFieldName`: the file | files field which must be send to the server + /// + ///[@var files: a List map of {"filePath": the file path, "fileName": the file ame}] + /// + ///if `hasSingleFile` is true, just the file first entry of the list otherwise looping through the ///list + /// + /// //Example of files format + /// // Even if, you want to upload a single file, you must send a list + /// var files = + /// [ + /// + /// { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' } + /// + /// ] + /// + /// // Or if multiple files + /// var files = + /// [ + /// + /// { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' }, + /// { 'filePath': '/data/shared/epatriote_bg.png', 'fileName': 'epatriote_bg.png' }, + /// { 'filePath': '/data/shared/epatriote_log_dark.png', 'fileName': 'epatriote_log_dark.png' } + /// + /// ] + /// + /// + /// Future> create( {String serviceName, Map data, @@ -237,8 +296,43 @@ class RestClient extends FlutterFeathersjs { return response; } - /// PUT /serviceName/_id - /// Completely replace a single resource. + /// `PUT /serviceName/_id` + /// + /// Completely replace a single resource with the `_id = objectId` + /// + /// The below is important if you have file to upload [containsFile == true] + /// + /// + ///@var `hasSingleFile`: true for signle file , false otherwise + /// + ///@ `fileFieldName`: the file | files field which must be send to the server + /// + ///[@var files: a List map of {"filePath": the file path, "fileName": the file ame}] + /// + ///if `hasSingleFile` is true, just the file first entry of the list otherwise looping through the + ///list + /// + /// //Example of files format + /// // Even if, you want to upload a single file, you must send a list + /// var files = + /// [ + /// + /// { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' } + /// + /// ] + /// + /// // Or if multiple files + /// var files = + /// [ + /// + /// { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' }, + /// { 'filePath': '/data/shared/epatriote_bg.png', 'fileName': 'epatriote_bg.png' }, + /// { 'filePath': '/data/shared/epatriote_log_dark.png', 'fileName': 'epatriote_log_dark.png' } + /// + /// ] + /// + /// + /// Future> update( {String serviceName, objectId, @@ -275,9 +369,43 @@ class RestClient extends FlutterFeathersjs { return response; } - /// PATCH /serviceName/_id - /// Merge the existing data of a single resources with the new data. - /// NOT TESTED + /// `PATCH /serviceName/_id` + /// + /// Merge the existing data of a single (`_id = objectId`) resource with the new `data` + /// + /// The below is important if you have file to upload [containsFile == true] + /// + /// + ///@var `hasSingleFile`: true for signle file , false otherwise + /// + ///@ `fileFieldName`: the file | files field which must be send to the server + /// + ///[@var files: a List map of {"filePath": the file path, "fileName": the file ame}] + /// + ///if `hasSingleFile` is true, just the file first entry of the list otherwise looping through the + ///list + /// + /// //Example of files format + /// // Even if, you want to upload a single file, you must send a list + /// var files = + /// [ + /// + /// { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' } + /// + /// ] + /// + /// // Or if multiple files + /// var files = + /// [ + /// + /// { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' }, + /// { 'filePath': '/data/shared/epatriote_bg.png', 'fileName': 'epatriote_bg.png' }, + /// { 'filePath': '/data/shared/epatriote_log_dark.png', 'fileName': 'epatriote_log_dark.png' } + /// + /// ] + /// + /// + /// Future> patch( {String serviceName, objectId, @@ -315,8 +443,9 @@ class RestClient extends FlutterFeathersjs { return response; } - /// DELETE /serviceName/_id - /// Remove a single resources: + /// `DELETE /serviceName/_id` + /// + /// Remove a single resource with `_id = objectId `: Future> remove({String serviceName, objectId}) async { var response; try { @@ -330,12 +459,40 @@ class RestClient extends FlutterFeathersjs { return response; } - ///@var fieldsMap: other field non file - ///@var hasSingleFile: true for signle file , false otherwise - ///@ fileFieldName: the file | files field which must be send to the server - ///@var files: a List map of {"filePath": the file path, "fileName": the file ame} - ///if hasSingleFile is true, just the file first entry of the list otherwise looping through the + ///@params `nonFilesFieldsMap`: other field non file + /// + ///@params `hasSingleFile`: `true` for signle file , `false` otherwise + /// + ///@params `fileFieldName`: the file | files field which must be send to the server + /// + ///@var `files`: a List map of `{"filePath": the file path, "fileName": the file name with extension}` + /// + /// `Example: { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' }` + /// + /// If `hasSingleFile` is true, just the file first entry of the list otherwise looping through the ///list + /// + /// //Example of files format + /// // Even if, you want to upload a single file, you must send a list + /// var files = + /// [ + /// + /// { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' } + /// + /// ] + /// + /// // Or if multiple files + /// var files = + /// [ + /// + /// { 'filePath': '/data/shared/epatriote_logo.png', 'fileName': 'epatriote_logo.png' }, + /// { 'filePath': '/data/shared/epatriote_bg.png', 'fileName': 'epatriote_bg.png' }, + /// { 'filePath': '/data/shared/epatriote_log_dark.png', 'fileName': 'epatriote_log_dark.png' } + /// + /// ] + /// + /// + /// Future makeFormData( {Map nonFilesFieldsMap, hasSingleFile = true, diff --git a/lib/src/scketio_client.dart b/lib/src/scketio_client.dart index 9d4333f..2bb346c 100644 --- a/lib/src/scketio_client.dart +++ b/lib/src/scketio_client.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:flutter_feathersjs/src/constants.dart'; import 'package:flutter_feathersjs/src/featherjs_client_base.dart'; import 'package:flutter_feathersjs/src/utils.dart'; @@ -7,11 +8,31 @@ import 'package:socket_io_client/socket_io_client.dart' as IO; import 'package:meta/meta.dart'; import 'package:event_bus/event_bus.dart'; +/// +/// ///Socketio client for the realtime communication +/// +///_______________________________________________ +///_______________________________________________ +/// +/// +/// `GENERAL NOTE 1`: If no error is occured, you will get an array +/// +/// Feathers data can be retrieve by doing +/// +/// `response[1]: => Feathers SocketIO `Method` Response Format` +/// +/// When an error occured, you will get a Json or Map object +/// +/// The format of the last one is according to what error occured on feather js server +/// +/// `GENERAL NOTE 2`: You cannot send file through this realtime client, please use rest client +/// class SocketioClient extends FlutterFeathersjs { IO.Socket _socket; - bool dev = true; + Utils utils; + EventBus eventBus = EventBus(); //Using singleton @@ -19,11 +40,10 @@ class SocketioClient extends FlutterFeathersjs { factory SocketioClient() { return _socketioClient; } - SocketioClient._internal(); - ///Anonymous connection - /// + SocketioClient._internal(); + /// Initialize the realtime (socketio) connection init({@required String baseUrl, Map extraHeaders}) { _socket = IO.io(baseUrl, { 'transports': ['websocket'], @@ -34,34 +54,38 @@ class SocketioClient extends FlutterFeathersjs { utils = new Utils(); + _socket.on('connect', (_) { + print("Socket connection established"); + eventBus.fire(Connected()); + }); - _socket.on('connect', (_) { - print("Socket connection established"); - eventBus.fire(Connected()); - }); - - _socket.on('connect_error', (e) { - print("Connection error"); - print(e); - }); - _socket.on('connect_timeout', (data) { - print("Timeout error"); - print(data); - }); - _socket.on('connecting', (_) => print("Connecting...")); - _socket.on('disconnect', (_) { - eventBus.fire(DisConnected()); - }); - _socket.on('error', (_) => print("An error occured")); - _socket.on('reconnect', (_) => print("Reconnected")); - _socket.on('reconnect_error', (_) => print("Reconnection error...")); - _socket.on( - 'reconnect_attempt', (_) => print("Attempting a reconnection")); - _socket.on('reconnect_failed', (_) => print("A reconnection failed")); - _socket.on('reconnecting', (_) => print("Reconnecting...")); + _socket.on('connect_error', (e) { + print("Connection error"); + print(e); + }); + _socket.on('connect_timeout', (data) { + print("Timeout error"); + print(data); + }); + _socket.on('connecting', (_) => print("Connecting...")); + _socket.on('disconnect', (_) { + eventBus.fire(DisConnected()); + }); + _socket.on('error', (_) => print("An error occured")); + _socket.on('reconnect', (_) => print("Reconnected")); + _socket.on('reconnect_error', (_) => print("Reconnection error...")); + _socket.on('reconnect_attempt', (_) => print("Attempting a reconnection")); + _socket.on('reconnect_failed', (_) => print("A reconnection failed")); + _socket.on('reconnecting', (_) => print("Reconnecting...")); } - ///This function must be call afther auth with rest is OK + /// + /// Authenticate the user with realtime connection + /// + /// @WARNING This function must be call afther auth with rest is OK + /// + ///Otherwise, you cannot be able to use socketio client because it won't be authed on the server + /// Future authWithJWT() async { //Get the existant accessToken //This cause you to call auth on rest before call this. @@ -82,11 +106,11 @@ class SocketioClient extends FlutterFeathersjs { } ], ack: (dataResponse) { print("Receive response from server on JWT request"); - if (dev) print(dataResponse); + print(dataResponse); //Check whether auth is OK if (dataResponse is List) { - if (dev) print("Is array"); + print("Authentication process is ok with JWT"); //Auth is ok var resp = dataResponse[1]; authResponse["error"] = false; @@ -98,7 +122,7 @@ class SocketioClient extends FlutterFeathersjs { 'Authorization': "Bearer $token" }; } else { - if (dev) print("Is not list"); + print("Authentication process failed with JWT"); //Auth is not ok authResponse["error"] = true; authResponse["message"] = dataResponse; @@ -110,67 +134,197 @@ class SocketioClient extends FlutterFeathersjs { return asyncTask.future; } - /// EMIT find serviceName - /// Retrieves a list of all matching resources from the service + /// `EMIT find serviceName` + /// + /// Retrieves a list of all matching `query` resources from the service + /// + /// NOTE: If no error is occured, you will get an array + /// + /// Feathers data can be retrieve by doing + /// + /// `response[1]: => Feathers SocketIO Find Response Format` + /// + /// When an error occured, you will get a Json or Map object + /// + /// The format of the last one is according to what error occured on feather js server + /// Future find({String serviceName, Map query}) async { Completer asyncTask = Completer(); _socket.emitWithAck("find", [serviceName, query], ack: (response) { - asyncTask.complete(response[1]); + asyncTask.complete(response); }); return asyncTask.future; } - /// EMIT create serviceName + /// `EMIT create serviceName` + /// /// Create new ressource + /// + /// `NOTE`: If no error is occured, you will get an array + /// + /// Feathers data can be retrieve by doing + /// + /// `response[1]: => Feathers SocketIO Create Response Format` + /// + /// When an error occured, you will get a Json or Map object + /// + /// The format of the last one is according to what error occured on feather js server + /// Future create({String serviceName, Map data}) { Completer asyncTask = Completer(); _socket.emitWithAck("create", [serviceName, data], ack: (response) { - asyncTask.complete(response[1]); + asyncTask.complete(response); }); return asyncTask.future; } - /// EMIT update serviceName + /// `EMIT update serviceName` + /// /// Update a ressource + /// + /// `NOTE`: If no error is occured, you will get an array + /// + /// Feathers data can be retrieve by doing + /// + /// `response[1]: => Feathers SocketIO Update Response Format` + /// + /// When an error occured, you will get a Json or Map object + /// + /// The format of the last one is according to what error occured on feather js server + /// Future update( {String serviceName, objectId, Map data}) { Completer asyncTask = Completer(); _socket.emitWithAck("update", [serviceName, objectId, data], ack: (response) { - asyncTask.complete(response[1]); + asyncTask.complete(response); }); return asyncTask.future; } - /// EMIT get serviceName - /// Retrieve a single ressource + /// `EMIT get serviceName` + /// + /// NOTE: If no error is occured, you will get an array + /// + /// Feathers data can be retrieve by doing + /// + /// `response[1]: => Feathers SocketIO Get Response Format` + /// + /// When an error occured, you will get a Json or Map object + /// + /// The format of the last one is according to what error occured on feather js server + /// Future get({String serviceName, objectId}) { Completer asyncTask = Completer(); _socket.emitWithAck("get", [serviceName, objectId], ack: (response) { - asyncTask.complete(response[1]); + asyncTask.complete(response); }); return asyncTask.future; } - /// EMIT patch serviceName + /// `EMIT patch serviceName` + /// ///Merge the existing data of a single or multiple resources with the new data + /// + /// NOTE: If no error is occured, you will get an array + /// + /// Feathers data can be retrieve by doing + /// + /// `response[1]: => Feathers SocketIO Patch Response Format` + /// + /// When an error occured, you will get a Json or Map object + /// + /// The format of the last one is according to what error occured on feather js server + /// Future patch( {String serviceName, objectId, Map data}) { Completer asyncTask = Completer(); _socket.emitWithAck("patch", [serviceName, objectId, data], ack: (response) { - asyncTask.complete(response[1]); + asyncTask.complete(response); }); return asyncTask.future; } - /// EMIT patch serviceName - ///Merge the existing data of a single or multiple resources with the new data + /// `EMIT remove serviceName` + /// + /// Delete a ressource on the server + /// + /// NOTE: If no error is occured, you will get an array + /// + /// Feathers data can be retrieve by doing + /// + /// `response[1]: => Feathers SocketIO Remove Response Format` + /// + /// When an error occured, you will get a Json or Map object + /// + /// The format of the last one is according to what error occured on feather js server + /// Future remove({String serviceName, objectId}) { Completer asyncTask = Completer(); _socket.emitWithAck("remove", [serviceName, objectId], ack: (response) { - asyncTask.complete(response[1]); + asyncTask.complete(response); }); return asyncTask.future; } + + /// Listen to `On created serviceName` + /// + /// The created event will be published with the + /// callback data, when a service create returns successfully. + /// + /// NOTE: The data you will get from the `StreamSubscription` + /// is exactly what feathers send `On created serviceName` + /// + Stream onCreated({@required String serviceName}) { + _socket.on('$serviceName created', (createdData) { + eventBus.fire(createdData); + }); + return eventBus.on(); + } + + /// Listen to `On removed serviceName` + /// + /// The removed event will be published + /// with the callback data, when a service remove calls back successfully + /// + /// NOTE: The data you will get from the `StreamSubscription` + /// is exactly what feathers send `On removed serviceName` + /// + Stream onRemoved({@required String serviceName}) { + _socket.on('$serviceName removed', (removedData) { + eventBus.fire(removedData); + }); + return eventBus.on(); + } + + /// Listen to `On updated | patched serviceName` + /// + /// The updated and patched events will be published with the callback data, + /// when a service update or patch method calls back successfully. + /// + /// NOTE: The data you will get from the `StreamSubscription` + /// is exactly what feathers send `On updated | patched serviceName` + /// + Stream onUpdated({@required String serviceName}) { + _socket.on('$serviceName updated', (updatedData) { + eventBus.fire(updatedData); + }); + return eventBus.on(); + } + + /// Listen to `On updated | patched serviceName` + /// + /// The updated and patched events will be published with the callback data, + /// when a service update or patch method calls back successfully. + /// + /// NOTE: The data you will get from the `StreamSubscription` + /// is exactly what feathers send `On updated | patched serviceName` + /// + Stream onPatched({@required String serviceName}) { + _socket.on('$serviceName patched', (patchedData) { + eventBus.fire(patchedData); + }); + return eventBus.on(); + } } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b281a0d..114afc4 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,26 +1,23 @@ //Help FlutterFeathersJs import 'dart:async'; +import 'package:flutter_feathersjs/src/constants.dart'; import 'package:shared_preferences/shared_preferences.dart'; class Utils { - ///////////////////////////////////////////////////////////////////// SharedPreferences prefs; - - //////////////////////////////////////////////////////////////////////// - Utils(); -//Allow to set rest access token + /// Store JWT for reAuth() purpose Future setAccessToken({String token}) async { prefs = await SharedPreferences.getInstance(); - return await prefs.setString('feathersjs_access_token', token); + return await prefs.setString(Constants.FEATHERSJS_ACCESS_TOKEN, token); } -//Allow to get rest access token + /// Get the early stored JWT for reAuth() purpose Future getAccessToken({String token}) async { prefs = await SharedPreferences.getInstance(); - return await prefs.getString('feathersjs_access_token'); + return await prefs.getString(Constants.FEATHERSJS_ACCESS_TOKEN); } } diff --git a/pubspec.yaml b/pubspec.yaml index c24e474..138e756 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_feathersjs description: Communicate with your feathers js server from flutter. -version: 0.0.7 +version: 0.1.0 homepage: https://github.com/Dahkenangnon/flutter_feathersjs.dart repository: https://github.com/Dahkenangnon/flutter_feathersjs.dart diff --git a/test/feathersjs_test.dart b/test/feathersjs_test.dart index 97706b7..ec80b60 100644 --- a/test/feathersjs_test.dart +++ b/test/feathersjs_test.dart @@ -23,150 +23,154 @@ void main() async { /// //////////////////////////////////////////////////// // Authenticate user and comment this line - var rep = await flutterFeathersjs.rest - .authenticate(email: user["email"], password: user["password"]); + var rep = await flutterFeathersjs.authenticate( + strategy: "phone", + userNameFieldName: "tel", + userName: user["tel"], + password: user["password"]); //Then use this one to reuse access token as it still valide //var reAuthResp = await flutterFeathersjs.rest.reAuthenticate(); - // ////////////////////////////////////////////////// - // / - // / Singleton testing - // / - // ////////////////////////////////////////////////// - - // Singleton pattern if the flutterFeathersjs class - test('Testing singleton ', () { - FlutterFeathersjs flutterFeathersjs1 = FlutterFeathersjs(); - expect(identical(flutterFeathersjs1, flutterFeathersjs), true); - }); //////////////////////////////////////////////////// /// - /// reAuth + /// Singleton testing + /// //////////////////////////////////////////////////// - //Testing the authentication method - test(' reAuthentication method', () async { - var reps = await flutterFeathersjs.reAuthenticate(); - if (!reps["error"]) { - print('client is authed'); - print("----------Authed user :------"); - print(reps["message"]); - print("----------Authed user :------"); - } else if (reps["error_zone"] == Constants.BOTH_CLIENT_AUTHED) - print("Blabal"); - else { - print(reps["error_zone"]); - print(reps["message"]); - print("frm secktio"); - print(reps["scketResponse"]); - print("frm rest"); - print(reps["restResponse"]); - } - }); + // // Singleton pattern if the flutterFeathersjs class + // test('Testing singleton ', () { + // FlutterFeathersjs flutterFeathersjs1 = FlutterFeathersjs(); + // expect(identical(flutterFeathersjs1, flutterFeathersjs), true); + // }); + + // // //////////////////////////////////////////////////// + // // /// + // // /// reAuth + // // //////////////////////////////////////////////////// + + // //Testing the authentication method + // test(' reAuthentication method', () async { + // var reps = await flutterFeathersjs.reAuthenticate(); + // if (!reps["error"]) { + // print('client is authed'); + // print("----------Authed user :------"); + // print(reps["message"]); + // print("----------Authed user :------"); + // } else if (reps["error_zone"] == Constants.BOTH_CLIENT_AUTHED) + // print("Blabal"); + // else { + // print(reps["error_zone"]); + // print(reps["message"]); + // print("frm secktio"); + // print(reps["scketResponse"]); + // print("frm rest"); + // print(reps["restResponse"]); + // } + // }); + + // //////////////////////////////////////////////////// + // /// + // /// Rest client methods + // /// + // /////////////////////////////////////////////////// + + // test('Testing singleton on Rest client', () { + // RestClient so1 = RestClient()..init(baseUrl: BASE_URL); + // RestClient so2 = RestClient()..init(baseUrl: BASE_URL); + // if (so1 == so2) { + // print("so1 == so2"); + // } + // expect(identical(so1, so2), true); + // }); + + // test(' \n rest Find all method \n', () async { + // var rep2 = await flutterFeathersjs.rest.find(serviceName: "v1/news"); + // print("\n Founds news are: \n"); + // print(rep2.data); + // }); + + // test(' \n rest find with with query params \n ', () async { + // var rep2 = await flutterFeathersjs.rest.find( + // serviceName: "v1/news", query: {"_id": "5f7643f0462f4348970cd32e"}); + // print("\n The news _id: 5f7643f0462f4348970cd32e is: \n"); + // print(rep2.data); + // }); + + // test(' \n Rest get method \n', () async { + // var rep2 = await flutterFeathersjs.rest + // .get(serviceName: "v1/news", objectId: "5f7643f0462f4348970cd32e"); + // print("\n The news _id: 5f7643f0462f4348970cd32e is: \n"); + // print(rep2.data); + // }); + + // test(' \n Rest Create method with user service \n ', () async { + // var rep2 = await flutterFeathersjs.rest.create( + // serviceName: "users", + // data: {"email": "user@email.com", "password": "password"}); + // print("\n The newly created user is: \n"); + // print(rep2); + // }); + + // test(' \n Rest create news with image (FormData support testing) \n ', + // () async { + // var data2Send = { + // "title": "formdata testing", + // "content": "yo", + // "by": "5f9aceba20bc3d3a74b7bf8b", + // "enterprise": "5f9acf4f20bc3d3a74b7bf91" + // }; + + // var files = [ + // {"filePath": "spin.PNG", "fileName": "spin.PNG"} + // ]; + + // var rep2 = await flutterFeathersjs.rest.create( + // serviceName: "v1/news", + // data: data2Send, + // containsFile: true, + // hasSingleFile: true, + // fileFieldName: "file", + // files: files); + // print("\n The new news menu created is: \n"); + // print(rep2.data); + // }); - //////////////////////////////////////////////////// - /// - /// Rest client methods - /// - /////////////////////////////////////////////////// - - test('Testing singleton on Rest client', () { - RestClient so1 = RestClient()..init(baseUrl: BASE_URL); - RestClient so2 = RestClient()..init(baseUrl: BASE_URL); - if (so1 == so2) { - print("so1 == so2"); - } - expect(identical(so1, so2), true); - }); - - test(' \n rest Find all method \n', () async { - var rep2 = await flutterFeathersjs.rest.find(serviceName: "v1/news"); - print("\n Founds news are: \n"); - print(rep2.data); - }); - - test(' \n rest find with with query params \n ', () async { - var rep2 = await flutterFeathersjs.rest.find( - serviceName: "v1/news", query: {"_id": "5f7643f0462f4348970cd32e"}); - print("\n The news _id: 5f7643f0462f4348970cd32e is: \n"); - print(rep2.data); - }); - - test(' \n Rest get method \n', () async { - var rep2 = await flutterFeathersjs.rest - .get(serviceName: "v1/news", objectId: "5f7643f0462f4348970cd32e"); - print("\n The news _id: 5f7643f0462f4348970cd32e is: \n"); - print(rep2.data); - }); - - test(' \n Rest Create method with user service \n ', () async { - var rep2 = await flutterFeathersjs.rest.create( - serviceName: "users", - data: {"email": "user@email.com", "password": "password"}); - print("\n The newly created user is: \n"); - print(rep2.data); - }); - - test(' \n Rest create news with image (FormData support testing) \n ', - () async { - var data2Send = { - "title": "formdata testing", - "content": "yo", - "by": "5f9aceba20bc3d3a74b7bf8b", - "enterprise": "5f9acf4f20bc3d3a74b7bf91" - }; - - var files = [ - {"filePath": "spin.PNG", "fileName": "spin.PNG"} - ]; - - var rep2 = await flutterFeathersjs.rest.create( - serviceName: "v1/news", - data: data2Send, - containsFile: true, - hasSingleFile: true, - fileFieldName: "file", - files: files); - print("\n The new news menu created is: \n"); - print(rep2.data); - }); - - ////////////////////////////////////////////////// - // - // Scketio client methods - // - ///////////////////////////////////////////////// - - test('Testing singleton on socketioClient', () { - SocketioClient so1 = SocketioClient()..init(baseUrl: BASE_URL); - SocketioClient so2 = SocketioClient()..init(baseUrl: BASE_URL); - if (so1 == so2) { - print("so1 == so2"); - } - expect(identical(so1, so2), true); - }); + // ////////////////////////////////////////////////// + // /// + // /// Scketio client methods + // /// + // ///////////////////////////////////////////////// + + // test('Testing singleton on socketioClient', () { + // SocketioClient so1 = SocketioClient()..init(baseUrl: BASE_URL); + // SocketioClient so2 = SocketioClient()..init(baseUrl: BASE_URL); + // if (so1 == so2) { + // print("so1 == so2"); + // } + // expect(identical(so1, so2), true); + // }); // //Testing the authentication with jwt method - test('socketio reAuthentication method', () async { - var repss = await flutterFeathersjs.scketio.authWithJWT(); - - //print(reps); - - if (!repss["error"]) { - print('client is authed'); - print(repss["message"]); - print(repss["error_zone"]); - } else { - print(repss["message"]); - print(repss["error_zone"]); - } - }); - - test('find method', () async { - var rep2 = await flutterFeathersjs.scketio.find( - serviceName: "v1/news", - ); - print("Printing news:"); - print(rep2); - }); + // test('socketio reAuthentication method', () async { + // var repss = await flutterFeathersjs.scketio.authWithJWT(); + + // //print(reps); + + // if (!repss["error"]) { + // print('client is authed'); + // print(repss["message"]); + // print(repss["error_zone"]); + // } else { + // print(repss["message"]); + // print(repss["error_zone"]); + // } + // }); + + // test('find method', () async { + // var rep2 = await flutterFeathersjs.scketio.find( + // serviceName: "v1/news", + // ); + // print("Printing news:"); + // print(rep2); + // }); } diff --git a/test/fixtures.dart b/test/fixtures.dart index c240029..3b6f6f7 100644 --- a/test/fixtures.dart +++ b/test/fixtures.dart @@ -1,9 +1,10 @@ //User credentials used for auth purpose Map user = { "strategy": "local", - "email": "user@dah.com", - "password": "user" + "email": "email@email.com", + "password": "email@email.com", + "tel": "+229000000" }; -//your use api -const BASE_URL = "https://api.domain.com"; +//your api +const BASE_URL = "https://api.my-domain.com";