diff --git a/Fuse.JSON/JSON.unoproj b/Fuse.JSON/JSON.unoproj new file mode 100755 index 0000000..caf3a7a --- /dev/null +++ b/Fuse.JSON/JSON.unoproj @@ -0,0 +1,13 @@ +{ + "Packages": [ + "Fuse", + "FuseJS", + ], + "Includes": [ + "Value.uno", + "ScriptingValue.uno", + "ObjCObject.uno", + "JavaObject.uno", + "Test.uno", + ] +} diff --git a/Fuse.JSON/JavaObject.uno b/Fuse.JSON/JavaObject.uno new file mode 100755 index 0000000..3ba948d --- /dev/null +++ b/Fuse.JSON/JavaObject.uno @@ -0,0 +1,87 @@ +using Uno; +using Uno.UX; +using Uno.Collections; +using Uno.Threading; +using Uno.IO; +using Uno.Compiler.ExportTargetInterop; +using Fuse; +using Fuse.Platform; +using Fuse.Scripting; +using Fuse.Reactive; + +namespace JSON +{ + public extern(Android) static class JavaObject + { + static Matcher _toJavaObject = new ToJavaObject(); + + public static Java.Object FromJSON(Value value) + { + return value.Match(_toJavaObject); + } + } + + [ForeignInclude(Language.Java,"java.lang.Object","java.util.List","java.util.ArrayList","java.util.Map","java.util.HashMap","android.util.Log")] + extern(Android) class ToJavaObject : Matcher + { + [Foreign(Language.Java)] + public Java.Object Case() @{ return null; @} + + [Foreign(Language.Java)] + public Java.Object Case(string str) @{ return str; @} + + [Foreign(Language.Java)] + public Java.Object Case(double num) @{ return (double) num; @} + + [Foreign(Language.Java)] + public Java.Object Case(bool b) @{ return (boolean) b; @} + + public Java.Object Case(IEnumerable> obj) + { + var dict = NewHashMap(); + + foreach (var keyValue in obj) + { + SetMapValue(dict, keyValue.Key, keyValue.Value); + } + + return dict; + } + + [Foreign(Language.Java)] + extern(Android) Java.Object NewHashMap() + @{ + return new HashMap(); + @} + + [Foreign(Language.Java)] + extern(Android) void SetMapValue(Java.Object dictHandle, string key, Java.Object value) + @{ + ((Map)dictHandle).put(key, value); + @} + + extern(Android) public Java.Object Case(IEnumerable array) + { + var result = NewArrayList(); + + foreach (var value in array) + { + AddArrayValue(result, value); + } + + return result; + } + + [Foreign(Language.Java)] + extern(Android) Java.Object NewArrayList() + @{ + return new ArrayList(); + @} + + [Foreign(Language.Java)] + extern(Android) void AddArrayValue(Java.Object arrayHandle, Java.Object value) + @{ + ((List) arrayHandle).add(value); + @} + } +} diff --git a/Fuse.JSON/LICENSE b/Fuse.JSON/LICENSE new file mode 100755 index 0000000..f127eb2 --- /dev/null +++ b/Fuse.JSON/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Olle Fredriksson + +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/Fuse.JSON/ObjCObject.uno b/Fuse.JSON/ObjCObject.uno new file mode 100755 index 0000000..a9e018e --- /dev/null +++ b/Fuse.JSON/ObjCObject.uno @@ -0,0 +1,82 @@ +using Uno.Collections; +using Uno.Compiler.ExportTargetInterop; + +namespace JSON +{ + public extern(iOS) static class ObjCObject + { + static Matcher _toObjCObject = new ToObjCObject(); + + public static ObjC.Object FromJSON(Value value) + { + return value.Match(_toObjCObject); + } + } + + extern(iOS) class ToObjCObject : Matcher + { + [Foreign(Language.ObjC)] + public ObjC.Object Case() @{ return [NSNull null]; @} + + [Foreign(Language.ObjC)] + public ObjC.Object Case(string str) @{ return str; @} + + [Foreign(Language.ObjC)] + public ObjC.Object Case(double num) @{ return [NSNumber numberWithDouble:num]; @} + + [Foreign(Language.ObjC)] + public ObjC.Object Case(bool b) @{ return [NSNumber numberWithBool:(b ? YES : NO)]; @} + + public ObjC.Object Case(IEnumerable> obj) + { + var dict = NewNSMutableDictionary(); + foreach (var keyValue in obj) + SetDictValue(dict, keyValue.Key, keyValue.Value); + return CopyToImmutableDict(dict); + } + + [Foreign(Language.ObjC)] + ObjC.Object NewNSMutableDictionary() + @{ + return [NSMutableDictionary dictionary]; + @} + + [Foreign(Language.ObjC)] + void SetDictValue(ObjC.Object dictHandle, string key, ObjC.Object value) + @{ + ((NSMutableDictionary*)dictHandle)[key] = value; + @} + + [Foreign(Language.ObjC)] + ObjC.Object CopyToImmutableDict(ObjC.Object dictHandle) + @{ + return [((NSMutableDictionary*)dictHandle) copy]; + @} + + public ObjC.Object Case(IEnumerable arr) + { + var result = NewNSMutableArray(); + foreach (var value in arr) + AddArrayValue(result, value); + return CopyToImmutableArray(result); + } + + [Foreign(Language.ObjC)] + ObjC.Object NewNSMutableArray() + @{ + return [NSMutableArray array]; + @} + + [Foreign(Language.ObjC)] + void AddArrayValue(ObjC.Object arrayHandle, ObjC.Object value) + @{ + [((NSMutableArray*)arrayHandle) addObject:value]; + @} + + [Foreign(Language.ObjC)] + ObjC.Object CopyToImmutableArray(ObjC.Object arrayHandle) + @{ + return [((NSMutableArray*)arrayHandle) copy]; + @} + } +} diff --git a/Fuse.JSON/ScriptingValue.uno b/Fuse.JSON/ScriptingValue.uno new file mode 100755 index 0000000..df3821b --- /dev/null +++ b/Fuse.JSON/ScriptingValue.uno @@ -0,0 +1,82 @@ +using Fuse.Scripting; +using Uno; +using Uno.Collections; + +namespace JSON +{ + public static class ScriptingValue + { + public static object FromJSON(Context context, Value value) + { + return value.Match(new ToScriptingValue(context)); + } + + public static Value ToJSON(object value) + { + if (value == null) return Null.TheNull; + if (value is string) return new String((string)value); + if (value is double) return new Number((double)value); + if (value is int) return new Number((int)value); + if (value is bool) return new Bool((bool)value); + if (value is Fuse.Scripting.Array) + { + var sarr = (Fuse.Scripting.Array)value; + var len = sarr.Length; + var arr = new Value[len]; + for (int i = 0; i < len; ++i) + { + arr[i] = ToJSON(sarr[i]); + } + return new Array(arr); + } + if (value is Fuse.Scripting.Object) + { + var sobj = (Fuse.Scripting.Object)value; + var dict = new Dictionary(); + foreach (var key in sobj.Keys) + { + dict[key] = ToJSON(sobj[key]); + } + return new Object(dict); + } + throw new ArgumentException("Not JSON-convertible", nameof(value)); + } + } + + class ToScriptingValue : Matcher + { + readonly Context _context; + + public ToScriptingValue(Context context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + _context = context; + } + + public object Case() { return null; } + public object Case(string str) { return str; } + public object Case(double num) { return num; } + public object Case(bool b) { return b; } + public object Case(IEnumerable> obj) + { + var result = _context.NewObject(); + foreach (var keyValue in obj) + { + result[keyValue.Key] = keyValue.Value; + } + return result; + } + public object Case(IEnumerable arr) + { + var result = _context.NewArray(); + int i = 0; + foreach (var val in arr) + { + result[i] = val; + ++i; + } + return result; + } + } +} diff --git a/Fuse.JSON/Test.uno b/Fuse.JSON/Test.uno new file mode 100755 index 0000000..1b27a80 --- /dev/null +++ b/Fuse.JSON/Test.uno @@ -0,0 +1,26 @@ +using Fuse.Scripting; +using Uno.Collections; +using Uno.Compiler.ExportTargetInterop; + +public class Test : Uno.Application +{ + public Test() + { + if defined(iOS) + { + var d = new Dictionary(); + d["hej"] = new JSON.String("hopp"); + d["happ"] = new JSON.String("hupp"); + d["arr"] = new JSON.Array(new JSON.Value[] { new JSON.String("arrayValue") }); + var jsonObject = new JSON.Object(d); + var test = JSON.ObjCObject.FromJSON(jsonObject); + NSLog(test); + } + } + + [Foreign(Language.ObjC)] + extern(iOS) static void NSLog(ObjC.Object o) + @{ + ::NSLog(@"%@", o); + @} +} diff --git a/Fuse.JSON/Value.uno b/Fuse.JSON/Value.uno new file mode 100755 index 0000000..2092722 --- /dev/null +++ b/Fuse.JSON/Value.uno @@ -0,0 +1,193 @@ +using Uno; +using Uno.Collections; + +namespace JSON +{ + public interface Matcher + { + T Case(); // Null + T Case(string str); + T Case(double num); + T Case(bool b); + T Case(IEnumerable> obj); + T Case(IEnumerable arr); + } + + public interface Value + { + T Match(Matcher matcher); + } + + public class Null : Value + { + public T Match(Matcher matcher) { return matcher.Case(); } + private Null() { } + public static Null TheNull = new Null(); + } + + public class String : Value + { + readonly string _value; + public String(string value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + _value = value; + } + public T Match(Matcher matcher) + { + return matcher.Case(_value); + } + } + + public class Number : Value + { + readonly double _value; + public Number(double value) { _value = value; } + public Number(int value) { _value = (double)value; } + public T Match(Matcher matcher) { return matcher.Case(_value); } + } + + public class Bool : Value + { + readonly bool _value; + public Bool(bool value) { _value = value; } + public T Match(Matcher matcher) { return matcher.Case(_value); } + } + + public class Object : Value + { + readonly Dictionary _dict; + + public Object(Dictionary dict) + { + if (dict == null) + throw new ArgumentNullException(nameof(dict)); + _dict = dict; + } + + public T Match(Matcher matcher) + { + return matcher.Case( + new ObjectEnumerable( + _dict, + matcher)); + } + + class ObjectEnumerable : IEnumerable> + { + Dictionary _dict; + Matcher _matcher; + + public ObjectEnumerable(Dictionary dict, Matcher matcher) + { + _dict = dict; + _matcher = matcher; + } + + public IEnumerator> GetEnumerator() + { + return new ObjectEnumerator(_dict.GetEnumerator(), _matcher); + } + } + + class ObjectEnumerator : IEnumerator> + { + IEnumerator> _enumerator; + Matcher _matcher; + + public ObjectEnumerator(IEnumerator> enumerator, Matcher matcher) + { + _enumerator = enumerator; + _matcher = matcher; + } + + public bool MoveNext() { return _enumerator.MoveNext(); } + public void Reset() { _enumerator.Reset(); } + + public KeyValuePair Current + { + get + { + var current = _enumerator.Current; + return new KeyValuePair(current.Key, current.Value.Match(_matcher)); + } + } + + public void Dispose() + { + _enumerator = null; + _matcher = null; + } + } + } + + public class Array : Value + { + readonly Value[] _arr; + + public Array(Value[] arr) + { + if (arr == null) + throw new ArgumentNullException(nameof(arr)); + _arr = arr; + } + + public T Match(Matcher matcher) + { + return matcher.Case( + new ArrayEnumerable( + _arr, + matcher)); + } + + class ArrayEnumerable : IEnumerable + { + readonly Value[] _arr; + readonly Matcher _matcher; + + public ArrayEnumerable(Value[] arr, Matcher matcher) + { + _arr = arr; + _matcher = matcher; + } + + public IEnumerator GetEnumerator() + { + return new ArrayEnumerator( + _arr, + _matcher); + } + } + + class ArrayEnumerator : IEnumerator + { + Value[] _arr; + int _index; + Matcher _matcher; + + public ArrayEnumerator(Value[] arr, Matcher matcher) + { + _arr = arr; + _index = -1; + _matcher = matcher; + } + + public bool MoveNext() + { + ++_index; + return _index < _arr.Length; + } + + public void Reset() { _index = -1; } + + public T Current { get { return _arr[_index].Match(_matcher); } } + + public void Dispose() + { + _arr = null; + _matcher = null; + } + } + } +} diff --git a/README.md b/README.md index c0ae73f..f1b81ac 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,329 @@ I'm an employee at Fuse, however any views, opinions or swearing at apis found i ## Obligatory Pic! ![wee](https://github.com/cbaggers/Fuse.Firebase/blob/master/docs/app.jpeg) + +## HOWTO + +### Create new record + +```JavaScript +var firebaseDb = require("Firebase/Database"); +var path = 'users'; +var data = { + name: 'Pavel' +}; + +// this will insert new object to given path +firebaseDb.pushWithTimestamp(path, data); + +// new object in database will have `timestamp` property set by Firebase server +{ + name: 'Pavel', + timestamp: 1517459417719 +} + +// if you want to receive event when record was created, read below +// if you do not want timestamp property to be created by firebase server +// use `.push(path, data)` method instead +``` + +### Read Data + +```JavaScript +// firebaseDb.read(path) + +var firebaseDb = require("Firebase/Database"); +var path = 'users/pavel'; +firebaseDb.read(path) + .then(function (json) { + // here json is a JSON string + console.log(json); + var user = JSON.parse(json); + }) + .catch(function (reason) { + console.log('Unable to read path: ' + path); + }); +``` + +### Read Data with Query + +Sometimes you need to search for a certain value in an object, instead or iterating in the client +use this implementation to let the Firebase do the query for you. + +The following example will give you all the objects where the key name is equal to Luis Rodriguez, +great for searching when you do not know the key and when you use Push to save data. + +```JavaScript +// firebaseDb.readByQueryEqualToValue(path,key,value) + +var firebaseDb = require("Firebase/Database"); +firebaseDb.readByQueryEqualToValue("users","name","Luis Rodriguez") + .then(function (json) { + // here json is a JSON string + console.log(json); + var user = JSON.parse(json); + }) + .catch(function (reason) { + console.log('Unable to read -> ' +reason); + }); +``` + +### Listen for data event + +* this event is fired when you change particular object. +* first you need to know the path to this object +* create listener +* subscribe to data event + +```JavaScript +var firebaseDb = require("Firebase/Database"); +var path1 = 'users/pavel'; +var path2 = 'users/mitul'; +// create listeners for data event for 2 paths +firebaseDb.listen(path1); +firebaseDb.listen(path2); + +// handle responses from Firebase +// you subscribe once, and will receive events for any path +// you're listening above +firebaseDb.on('data', function (eventPath, msg) { + // msg here is a JSON string + // track only given path + if (eventPath === path1) { + console.log(path1); + console.log(msg); + // convert JSON to object + var user1 = JSON.parse(msg); + } + if (eventPath === path2) { + console.log(path2); + console.log(msg); + // convert JSON to object + var user2 = JSON.parse(msg); + } + }); +``` + +### Listen for dataAdded events + +```JavaScript +var firebaseDb = require("Firebase/Database"); +var usersPath = 'users'; +var messagesPath = 'messages'; + +// create listener for given path +// 1 means that you will always receive last object from `path` after you call `.listenOnAdded` +// this doesn't means that this object was just added, it's just how firebase iOS/Android library works +// more on that: https://firebase.google.com/docs/database/ios/lists-of-data +// find FIRDataEventTypeChildAdded and read how it works. It's not our fault :) +// so you need to take care about that and do not insert same record twice +// it's not possible to set it to 0, so 1 is a safe default +// if you set it to 10 you'll get last 10 records from given `path` + +firebaseDb.listenOnAdded(usersPath, 1); +firebaseDb.listenOnAdded(messagesPath, 1); + +// now subscribe to `dataAdded` events +// you need to subscribe only once +// function below will be executed for any path +firebaseDb.on('dataAdded', function (eventPath, msg) { + // msg here is a JSON string + + // track only given path + if (eventPath === usersPath) { + // new user record was added, usually by push method + // if you created record using not .push method or + // pushWithTimestamp you will not receive event here + console.log(eventPath); + console.log(msg); + var newUser = JSON.parse(msg); + } + + if (eventPath === messagesPath) { + // new message record was added, usually by push method + // if you created record using not .push method or + // .pushWithTimestamp you will not receive event here + console.log(eventPath); + console.log(msg); + var newMessage = JSON.parse(msg); + } + }); + +``` + +### Listen for dataRemoved events + +```JavaScript +var firebaseDb = require("Firebase/Database"); +var usersPath = 'users'; +var messagesPath = 'messages'; + +// create listener for given path +// 1 means that you will always receive last object from `path` +// it's not possible to set it to 0, so 1 is a safe default +// if you set it to 10 you'll get last 10 records from given `path` + +firebaseDb.listenOnRemoved(usersPath, 1); +firebaseDb.listenOnRemoved(messagesPath, 1); + +// now subscribe to `dataAdded` events +// you need to subscribe only once +// function below will be executed for any path +firebaseDb.on('dataRemoved', function (eventPath, msg) { + // msg here is a JSON string + + // track only given path + if (eventPath === usersPath) { + // user record was removed + console.log(eventPath); + console.log(msg); + var newUser = JSON.parse(msg); + } + + if (eventPath === messagesPath) { + // message record was removed + console.log(eventPath); + console.log(msg); + var newMessage = JSON.parse(msg); + } +}); + +``` + +### Query subset of records + +* say you want to get old messages for particular user +* you want to get messages before particular timestamp + +```JavaScript +var firebaseDb = require("Firebase/Database"); +var chatMessagesPath = 'users/pavel/messages'; +var oldestMessageTimestamp = 1517459417719; +var count = 20; + +// we're going to query 20 messages before `oldestMessageTimestamp` + +firebaseDb.readByQueryEndingAtValue( + chatMessagesPath, + 'timestamp', // field we're comaring with + oldestMessageTimestamp, // oldest message timestamp on our device + count // how many records to get +); + +// you need to do this only once in your code +// this event handler will be called for every firebaseDb.readByQueryEndingAtValue method +firebaseDb.on('readByQueryEndingAtValue', function (eventPath, newMessages) { + // newMessages here is a JSON string + if (eventPath === chatMessagesPath) { + console.log(chatMesssagesPath); + console.log(newMessages); + var data = JSON.parse(newMessages); + } else { + // somebody requested data for other firebase path + } +}); +``` + +### Detach Listeners + +* when you don't want to receive events for particular path anymore, you need to detach listener + +```JavaScript +var firebaseDb = require("Firebase/Database"); +var usersPath = 'users'; +// say you subscribed to some path earlier +firebaseDb.listenOnRemoved(usersPath, 1); +// and now you don't want to receive dataRemoved events anymore +// maybe user was logged out etc. + +// do this, however this will also unsubscribe from `dataAdded` and `data` events +firebaseDb.detachListeners(usersPath); +``` + +### Upload an Image to Firebase + +* Upload an image using Camera + +```JavaScript +var Camera = require('FuseJS/Camera'); +var Storage = require('Firebase/Storage'); + +var imagePath = "images/test.jpg"; +Camera.takePicture(640,480).then(function(image) { + + Storage.upload(imagePath, image.path) // Make sure you use the path + .then(function(url) { + console.log('url' + url); // Receives the URL as a promise + }).catch(function(err) { + console.log('err' + err); + }) +}).catch(function(error) { + //Something went wrong, see error for details + console.log('error ' + error); +}); +``` + +* Upload an image using CameraRoll/Gallery + +```JavaScript +var CameraRoll = require("FuseJS/CameraRoll"); +var Storage = require('Firebase/Storage'); + +var imagePath = "images/test.jpg"; +CameraRoll.getImage() +.then(function(image) { + Storage.upload(imagePath, image.path) // Make sure you use the path + .then(function(url) { + console.log('url ' + url); // Receives the URL as a promise + }).catch(function(err) { + console.log('err ' + err); + }) +}, function(error) { + // Will be called if the user aborted the selection or if an error occurred. + console.log('error ' + error); +``` + +### Simple Storage +* For now, this uploads a picture to FirebaseStorage and returns the URL to access it. Part of this code was written by [Bolav](https://github.com/bolav). + +* Works great on both iOS and Android. + +* Also usable with `CameraRoll` and `ImageTools` + +```Javascript +var Camera = require("FuseJS/Camera"); +var Storage = require("Firebase/Storage"); +var path = "pictures/cats/mycat.jpg" + +Camera.takePicture(640,480).then(function(image) +{ + Storage.upload(path,image.path) // Make sure you use the path + .then(function(url){ + console.log(url); // Receives the URL as a promise + }).catch(function(err){ + console.log(err) + }) +}).catch(function(error) { + //Something went wrong, see error for details +}); +``` + +### Read Data with Query + +* Get all the objects where the key name is equal to pavel + +```Javascript +var path = user; +var key = name; +var value = ‘pavel’; + +firebaseDb.readByQueryEqualToValue(path, key, value) +.then(function (json) { + // here json is a JSON string + console.log('json '+ json); + var user = JSON.parse(json); +}) +.catch(function (reason) { + console.log('Unable to read -> ' + reason); +}); +``` diff --git a/src/Firebase.AdMob/AdMob.uno b/src/Firebase.AdMob/AdMob.uno index d2f8c5e..e536e43 100644 --- a/src/Firebase.AdMob/AdMob.uno +++ b/src/Firebase.AdMob/AdMob.uno @@ -49,7 +49,6 @@ namespace Firebase.AdMob if defined(Android) { if (AdUnitId == null) { - debug_log "No AdUnitId"; throw new Uno.Exception("Not initialized."); } _native = new AndroidGADBannerView(AdUnitId); @@ -58,7 +57,6 @@ namespace Firebase.AdMob else if defined(iOS) { if (AdUnitId == null) { - debug_log "No AdUnitId"; throw new Uno.Exception("Not initialized."); } _native = new iOSGADBannerView(AdUnitId); diff --git a/src/Firebase.Analytics/Analytics.uno b/src/Firebase.Analytics/Analytics.uno index bb771ff..d9e9127 100644 --- a/src/Firebase.Analytics/Analytics.uno +++ b/src/Firebase.Analytics/Analytics.uno @@ -66,8 +66,6 @@ namespace Firebase.Analytics internal static class AnalyticsService { public static void Init() {} - public static void LogEvent(string name, string[] keys, string[] vals, int len) { - debug_log "LogEvent: " + name; - } + public static void LogEvent(string name, string[] keys, string[] vals, int len) { } } } diff --git a/src/Firebase.Authentication.Email/AndroidImpl.uno b/src/Firebase.Authentication.Email/AndroidImpl.uno index 45a9f2a..e6e5f88 100644 --- a/src/Firebase.Authentication.Email/AndroidImpl.uno +++ b/src/Firebase.Authentication.Email/AndroidImpl.uno @@ -18,7 +18,7 @@ namespace Firebase.Authentication.Email "com.google.firebase.auth.AuthResult", "com.google.firebase.auth.FirebaseAuth", "com.google.firebase.auth.FirebaseUser")] - [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:9.2.0")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:11.8.0")] extern(android) internal class CreateUser : Promise { @@ -36,8 +36,13 @@ namespace Firebase.Authentication.Email { if (task.isSuccessful()) @{CreateUser:Of(_this).Resolve(string):Call("success")}; - else - @{CreateUser:Of(_this).Reject(string):Call("Firebase failed to create user")}; + else { + try { + @{CreateUser:Of(_this).Reject(string):Call(task.getException().getLocalizedMessage())}; + } catch (Exception e) { + @{CreateUser:Of(_this).Reject(string):Call("Firebase failed to create user")}; + } + } } }); @} @@ -60,7 +65,7 @@ namespace Firebase.Authentication.Email "com.google.firebase.auth.AuthResult", "com.google.firebase.auth.FirebaseAuth", "com.google.firebase.auth.FirebaseUser")] - [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:9.2.0")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:11.8.0")] extern(android) internal class SignInUser : Promise { @@ -76,10 +81,15 @@ namespace Firebase.Authentication.Email .addOnCompleteListener(com.fuse.Activity.getRootActivity(), new OnCompleteListener() { public void onComplete(Task task) { - if (task.isSuccessful()) - @{SignInUser:Of(_this).Resolve(string):Call("success")}; - else - @{SignInUser:Of(_this).Reject(string):Call("Firebase failed to signin user")}; + if (task.isSuccessful()) + @{SignInUser:Of(_this).Resolve(string):Call("success")}; + else { + try { + @{SignInUser:Of(_this).Reject(string):Call(task.getException().getLocalizedMessage())}; + } catch (Exception e) { + @{SignInUser:Of(_this).Reject(string):Call("Firebase failed to signin user")}; + } + } } }); @} @@ -103,7 +113,7 @@ namespace Firebase.Authentication.Email "com.google.firebase.auth.EmailAuthProvider", "com.google.firebase.auth.AuthCredential", "com.google.firebase.auth.UserProfileChangeRequest")] - [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:9.2.0")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:11.8.0")] extern(android) internal class ReAuthenticate : Promise { diff --git a/src/Firebase.Authentication.Facebook/FacebookAuthentication.uno b/src/Firebase.Authentication.Facebook/FacebookAuthentication.uno new file mode 100644 index 0000000..a6b9bc0 --- /dev/null +++ b/src/Firebase.Authentication.Facebook/FacebookAuthentication.uno @@ -0,0 +1,288 @@ +using Fuse; +using Fuse.Platform; +using Uno; +using Uno.Compiler.ExportTargetInterop; +using Firebase.Authentication; + + +namespace Firebase.Authentication.Facebook.JS +{ + +[extern(Android) ForeignInclude(Language.Java, + "com.fuse.Activity", + "android.content.Intent", + "com.facebook.*", + "com.facebook.login.*", + "com.facebook.appevents.AppEventsLogger", + "com.google.android.gms.tasks.OnCompleteListener", + "com.google.android.gms.tasks.Task", + "com.google.firebase.auth.*")] + + [extern(iOS) Require("Cocoapods.Podfile.Target", "pod 'FBSDKCoreKit'")] + [extern(iOS) Require("Cocoapods.Podfile.Target", "pod 'FBSDKShareKit'")] + [extern(iOS) Require("Cocoapods.Podfile.Target", "pod 'FBSDKLoginKit'")] + [extern(iOS) Require("AppDelegate.SourceFile.Declaration", "#include ")] + [extern(iOS) Require("Source.Include", "Firebase/Firebase.h")] + [extern(iOS) Require("Source.Include", "Firebase/Firebase.h")] + [extern(iOS) Require("Source.Include", "FirebaseAuth/FirebaseAuth.h")] + [extern(iOS) Require("Source.Include", "FBSDKCoreKit/FBSDKCoreKit.h")] + [extern(iOS) Require("Source.Include", "FBSDKLoginKit/FBSDKLoginKit.h")] + [extern(iOS) Require("Source.Include", "@{Firebase.Authentication.Facebook.JS.FacebookModule:Include}")] + [extern(iOS) Require("Source.Include", "@{FacebookService:Include}")] + + public class FacebookAuthentication + { + + public FacebookAuthentication() + { + + } + + [Foreign(Language.ObjC)] + public extern(iOS) void Login() + @{ + FBSDKLoginManager* login = [[FBSDKLoginManager alloc] init]; + [login + logInWithReadPermissions: @[@"public_profile"] + fromViewController: [[[UIApplication sharedApplication] keyWindow] rootViewController] + handler: ^(FBSDKLoginManagerLoginResult* result, NSError* error) + { + if (error) + { + NSString * message = @"Facebook SignIn Failed. Error code: "; + NSString *errorMessage = [NSString stringWithFormat:@"%@ %ld", message, error.code]; + @{OnFailure(string):Call(errorMessage)}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call(errorMessage)}; + return; + } + if (result.isCancelled) + { + NSString *error = @"Facebook Auth Stage Cancelled"; + @{OnFailure(string):Call(error)}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call(error)}; + return; + } + @{OnAuth(ObjC.Object):Call(result)}; + } + ]; + @} + + extern(Android) Java.Object _callbackManager; + [Foreign(Language.Java)] + public extern(Android) void Login() + @{ + FacebookSdk.sdkInitialize(Activity.getRootActivity()); + final CallbackManager callbackManager = CallbackManager.Factory.create(); + @{FacebookAuthentication:Of(_this)._callbackManager:Set(callbackManager)}; + Activity.subscribeToResults(new Activity.ResultListener() { + @Override + public boolean onResult(int requestCode, int resultCode, Intent data) { + return callbackManager.onActivityResult(requestCode, resultCode, data); + } + }); + CallbackManager callbackManagerFrom = (CallbackManager)@{FacebookAuthentication:Of(_this)._callbackManager:Get()}; + LoginManager.getInstance().registerCallback(callbackManagerFrom, + new FacebookCallback() + { + public void onSuccess(LoginResult loginResult) { + @{OnAuth(Java.Object):Call(loginResult.getAccessToken())}; + } + + public void onCancel() { + @{OnFailure(string):Call("Facebook Auth Stage Cancelled")}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call("Facebook Auth Stage Cancelled")}; + } + + public void onError(FacebookException error) { + String reason = "Facebook Auth Stage Errored (" + error.getClass().getName() + "):\n" + error.getMessage(); + @{OnFailure(string):Call(reason)}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call(reason)}; + } + } + ); + LoginManager.getInstance().logInWithReadPermissions(Activity.getRootActivity(), java.util.Arrays.asList("public_profile")); + @} + + extern(Mobile) + static void OnSuccess(string message) + { + AuthService.AnnounceSignIn(AuthProviderName.Facebook); + } + + extern(Mobile) + static void OnFailure(string reason) + { + AuthService.SignalError(-1, reason); + } + + [Foreign(Language.Java)] + static extern(Android) void OnAuth(Java.Object result) + @{ + final AccessToken token = (AccessToken)result; + AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken()); + FirebaseAuth.getInstance().signInWithCredential(credential) + .addOnCompleteListener(com.fuse.Activity.getRootActivity(), new OnCompleteListener() { + public void onComplete(Task task) { + if (task.isSuccessful()) { + @{Firebase.Authentication.Facebook.JS.FacebookModule.Auth(string):Call(token.getToken())}; + @{OnSuccess(string):Call("Success")}; + } + else + @{OnFailure(string):Call("Authentication against Firebase failed")}; + }}); + @} + + [Foreign(Language.ObjC)] + static extern(iOS) void OnAuth(ObjC.Object result) + @{ + FBSDKLoginManagerLoginResult *accessToken = (FBSDKLoginManagerLoginResult *)result; + NSString *tokenStr = accessToken.token.tokenString; + + if (tokenStr==NULL) + { + @{OnFailure(string):Call(@"Authentication against Firebase failed")}; + return; + } + + FIRAuthCredential* credential = [FIRFacebookAuthProvider credentialWithAccessToken:tokenStr]; + + // auth againsts firebase + [[FIRAuth auth] signInWithCredential:credential + completion:^(FIRUser* fuser, NSError* ferror) { + if (ferror) + @{OnFailure(string):Call(@"Authentication against Firebase failed")}; + else { + @{Firebase.Authentication.Facebook.JS.FacebookModule.Auth(string):Call(tokenStr)}; + @{OnSuccess(string):Call(@"success")}; + } + }]; + @} + + + [Foreign(Language.ObjC)] + public extern(iOS) void LinkFacebook() + @{ + FBSDKLoginManager* login = [[FBSDKLoginManager alloc] init]; + [login + logInWithReadPermissions: @[@"public_profile"] + fromViewController: [[[UIApplication sharedApplication] keyWindow] rootViewController] + handler: ^(FBSDKLoginManagerLoginResult* result, NSError* error) + { + if (error) + { + NSString * message = @"Facebook SignIn Failed. Error code: "; + NSString *errorMessage = [NSString stringWithFormat:@"%@ %ld", message, error.code]; + @{OnFailure(string):Call(errorMessage)}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call(errorMessage)}; + return; + } + if (result.isCancelled) + { + NSString *error = @"Facebook Auth Stage Cancelled"; + @{OnFailure(string):Call(error)}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call(error)}; + return; + } + @{OnLinkFacebookWithFirebase(ObjC.Object):Call(result)}; + } + ]; + @} + + [Foreign(Language.Java)] + public extern(Android) void LinkFacebook() + @{ + FacebookSdk.sdkInitialize(Activity.getRootActivity()); + final CallbackManager callbackManager = CallbackManager.Factory.create(); + @{FacebookAuthentication:Of(_this)._callbackManager:Set(callbackManager)}; + Activity.subscribeToResults(new Activity.ResultListener() { + @Override + public boolean onResult(int requestCode, int resultCode, Intent data) { + return callbackManager.onActivityResult(requestCode, resultCode, data); + } + }); + CallbackManager callbackManagerFrom = (CallbackManager)@{FacebookAuthentication:Of(_this)._callbackManager:Get()}; + LoginManager.getInstance().registerCallback(callbackManagerFrom, + new FacebookCallback() + { + public void onSuccess(LoginResult loginResult) { + @{OnLinkFacebookWithFirebase(Java.Object):Call(loginResult.getAccessToken())}; + } + + public void onCancel() { + @{OnFailure(string):Call("Facebook Auth Stage Cancelled")}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call("Facebook Auth Stage Cancelled")}; + } + + public void onError(FacebookException error) { + String reason = "Facebook Auth Stage Errored (" + error.getClass().getName() + "):\n" + error.getMessage(); + @{OnFailure(string):Call(reason)}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call(reason)}; + } + } + ); + LoginManager.getInstance().logInWithReadPermissions(Activity.getRootActivity(), java.util.Arrays.asList("public_profile")); + @} + + + [Foreign(Language.ObjC)] + static extern(iOS) void OnLinkFacebookWithFirebase(ObjC.Object result) + @{ + FBSDKLoginManagerLoginResult *accessToken = (FBSDKLoginManagerLoginResult *)result; + NSString *tokenStr = accessToken.token.tokenString; + + if (tokenStr==NULL) + { + @{OnFailure(string):Call(@"Authentication against Firebase failed")}; + return; + } + + FIRAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:tokenStr]; + + [[FIRAuth auth].currentUser linkAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *result, NSError *_Nullable error) { + if (error) { + if (error.code == FIRAuthErrorCodeProviderAlreadyLinked) { + @{Firebase.Authentication.Facebook.JS.FacebookModule.Auth(string):Call(tokenStr)}; + @{OnSuccess(string):Call(@"success")}; + } else { + @{OnFailure(string):Call(@"Link Firebase failed")}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call(error.localizedDescription)}; + } + } else { + @{Firebase.Authentication.Facebook.JS.FacebookModule.Auth(string):Call(tokenStr)}; + @{OnSuccess(string):Call(@"success")}; + } + }]; + @} + + [Foreign(Language.Java)] + static extern(Android) void OnLinkFacebookWithFirebase(Java.Object result) + @{ + final AccessToken token = (AccessToken)result; + AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken()); + FirebaseAuth.getInstance().getCurrentUser().linkWithCredential(credential) + .addOnCompleteListener(com.fuse.Activity.getRootActivity(), new OnCompleteListener() { + @Override + public void onComplete(Task task) { + if (task.isSuccessful()) { + @{Firebase.Authentication.Facebook.JS.FacebookModule.Auth(string):Call(token.getToken())}; + @{OnSuccess(string):Call("Success")}; + } else { + if (task.getException() != null) { + if (task.getException().getMessage().equals("User has already been linked to the given provider.")) { + @{Firebase.Authentication.Facebook.JS.FacebookModule.Auth(string):Call(token.getToken())}; + @{OnSuccess(string):Call("Success")}; + } else { + @{OnFailure(string):Call("Authentication against Firebase failed")}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call(task.getException().getMessage())}; + } + } else { + @{OnFailure(string):Call("Authentication against Firebase failed")}; + @{Firebase.Authentication.Facebook.JS.FacebookModule.OnFailed(string):Call("Link Firebase failed")}; + } + } + } + }); + @} + } +} \ No newline at end of file diff --git a/src/Firebase.Authentication.Facebook/JS.uno b/src/Firebase.Authentication.Facebook/JS.uno index 6c83437..98a9b76 100644 --- a/src/Firebase.Authentication.Facebook/JS.uno +++ b/src/Firebase.Authentication.Facebook/JS.uno @@ -17,22 +17,55 @@ namespace Firebase.Authentication.Facebook.JS public sealed class FacebookModule : NativeModule { static readonly FacebookModule _instance; - static NativeEvent _onAuth; + static NativeEvent _onAuth, _onError; + readonly FacebookAuthentication _facebookAuthentication; public FacebookModule() { if(_instance != null) return; Uno.UX.Resource.SetGlobalKey(_instance = this, "Firebase/Authentication/Facebook"); - + _facebookAuthentication = new FacebookAuthentication(); _onAuth = new NativeEvent("onAuth"); AddMember(_onAuth); + _onError = new NativeEvent("onFailed"); + AddMember(_onError); + AddMember(new NativeFunction("doFacebookLogin", (NativeCallback)DoFacebookLogin)); + AddMember(new NativeFunction("linkFacebook", (NativeCallback)LinkFacebook)); Firebase.Authentication.Facebook.FacebookService.Init(); } static void Auth(string token) { - _onAuth.RaiseAsync(token); + _onAuth.RaiseAsync(_onAuth.ThreadWorker, token); + //_onAuth.RaiseAsync(token); + } + + static void OnFailed(string err) + { + _onError.RaiseAsync(_onError.ThreadWorker, err); + //_onError.RaiseAsync(err); } + + object DoFacebookLogin(Context context, object[] args) + { + if defined(iOS || Android) + { + _facebookAuthentication.Login(); + } + else{ } + return null; + } + + object LinkFacebook(Context context, object[] args) + { + if defined(iOS || Android) + { + _facebookAuthentication.LinkFacebook(); + } + else{ } + return null; + } + } -} +} \ No newline at end of file diff --git a/src/Firebase.Authentication.Google/Authentication.uno b/src/Firebase.Authentication.Google/Authentication.uno index ef4f676..e612c61 100644 --- a/src/Firebase.Authentication.Google/Authentication.uno +++ b/src/Firebase.Authentication.Google/Authentication.uno @@ -30,7 +30,7 @@ namespace Firebase.Authentication.Google "com.google.firebase.auth.FirebaseAuth", "com.google.firebase.auth.FirebaseUser", "com.google.firebase.auth.GoogleAuthProvider")] - [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:9.2.0")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:11.8.0")] [Require("Gradle.Dependency.Compile", "com.google.android.gms:play-services-auth:9.2.0")] [Require("Cocoapods.Podfile.Target", "pod 'GoogleSignIn'")] [Require("AppDelegate.HeaderFile.Declaration", "#include ")] diff --git a/src/Firebase.Authentication.Google/JS.uno b/src/Firebase.Authentication.Google/JS.uno index cb9431e..53e4715 100644 --- a/src/Firebase.Authentication.Google/JS.uno +++ b/src/Firebase.Authentication.Google/JS.uno @@ -31,7 +31,8 @@ namespace Firebase.Authentication.Google.JS static void Auth(string idToken, string accessToken) { - _onAuth.RaiseAsync(idToken, accessToken); + _onAuth.RaiseAsync(_onAuth.ThreadWorker, idToken, accessToken); + //_onAuth.RaiseAsync(idToken, accessToken); } } } diff --git a/src/Firebase.Authentication.Phone/AndroidImpl.uno b/src/Firebase.Authentication.Phone/AndroidImpl.uno new file mode 100644 index 0000000..987338f --- /dev/null +++ b/src/Firebase.Authentication.Phone/AndroidImpl.uno @@ -0,0 +1,321 @@ +using Uno; +using Uno.UX; +using Uno.Collections; +using Uno.Compiler.ExportTargetInterop; +using Fuse; +using Fuse.Triggers; +using Fuse.Controls; +using Fuse.Controls.Native; +using Fuse.Controls.Native.Android; +using Uno.Threading; +using Firebase.Authentication; + +namespace Firebase.Authentication.Phone +{ + [ForeignInclude(Language.Java, "java.util.ArrayList", "java.util.List", "android.content.Intent", + "java.util.concurrent.TimeUnit", + "android.content.SharedPreferences", + "android.util.Log", + "android.preference.PreferenceManager", + "com.google.android.gms.tasks.OnCompleteListener", + "com.google.android.gms.tasks.Task", + "com.google.firebase.auth.PhoneAuthProvider", + "com.google.firebase.auth.PhoneAuthCredential", + "com.google.firebase.auth.FirebaseAuth", + "com.google.firebase.auth.AuthResult", + "com.google.firebase.FirebaseException", + "com.google.firebase.FirebaseTooManyRequestsException", + "com.google.firebase.auth.FirebaseAuthInvalidCredentialsException")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:11.8.0")] + extern(android) + internal class CreateUser : Promise + { + + extern(android) static internal Java.Object mResendToken; + + public CreateUser(string phone) + { + Init(phone); + } + + [Foreign(Language.Java)] + public void Init(string phone) + @{ + PhoneAuthProvider.OnVerificationStateChangedCallbacks mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { + + @Override + public void onVerificationCompleted(PhoneAuthCredential credential) { + // This callback will be invoked in two situations: + // 1 - Instant verification. In some cases the phone number can be instantly + // verified without needing to send or enter a verification code. + // 2 - Auto-retrieval. On some devices Google Play services can automatically + // detect the incoming verification SMS and perform verification without + // user action. + Log.d("Log ", "onVerificationCompleted:" + credential); + FirebaseAuth.getInstance().signInWithCredential(credential) + .addOnCompleteListener(com.fuse.Activity.getRootActivity(), new OnCompleteListener() { + public void onComplete(Task task) { + if (task.isSuccessful()) { + @{CreateUser:Of(_this).Auth(string):Call(task.getResult().getUser().getIdToken(false).getResult().getToken())}; + } + else { + Log.d("Log ", " " + task.getException().toString()); + @{CreateUser:Of(_this).Reject(string):Call(task.getException().getLocalizedMessage())}; + } + }}); + } + + @Override + public void onVerificationFailed(FirebaseException e) { + // This callback is invoked in an invalid request for verification is made, + // for instance if the the phone number format is not valid. + Log.w("Log ", "onVerificationFailed", e); + + if (e instanceof FirebaseAuthInvalidCredentialsException) { + // Invalid request + // ... + @{CreateUser:Of(_this).Reject(string):Call("The country code or mobile number may be incorrect")}; + return; + } else if (e instanceof FirebaseTooManyRequestsException) { + // The SMS quota for the project has been exceeded + // ... + } + + @{CreateUser:Of(_this).Reject(string):Call("SMS failed")}; + + // Show a message and update the UI + // ... + } + + @Override + public void onCodeSent(String verificationId, + PhoneAuthProvider.ForceResendingToken token) { + // The SMS verification code has been sent to the provided phone number, we + // now need to ask the user to enter the code and then construct a credential + // by combining the code with a verification ID. + Log.d("Log ", "onCodeSent:" + verificationId); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(com.fuse.Activity.getRootActivity()); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("authVerificationID", verificationId); + editor.apply(); + @{mResendToken:Set(token)}; + @{Firebase.Authentication.Phone.JS.PhoneModule.CodeSent(string):Call("Code sent")}; + } + }; + + PhoneAuthProvider.getInstance().verifyPhoneNumber( + phone, // Phone number to verify + 60, // Timeout duration + TimeUnit.SECONDS, // Unit of timeout + com.fuse.Activity.getRootActivity(), // Activity (for callback binding) + mCallbacks); + + @} + + void Resolve(string message) + { + base.Resolve(message); + } + + void Reject(string reason) + { + AuthService.SignalError(-1, reason); + Reject(new Exception(reason)); + } + + void Auth(string message) { + AuthService.AnnounceSignIn(AuthProviderName.Phone); + base.Resolve(message); + } + + } + + [ForeignInclude(Language.Java, "android.content.SharedPreferences", + "android.preference.PreferenceManager", + "com.google.android.gms.tasks.OnCompleteListener", + "com.google.android.gms.tasks.Task", + "com.google.firebase.auth.PhoneAuthProvider", + "com.google.firebase.auth.PhoneAuthCredential", + "com.google.firebase.auth.FirebaseAuth", + "com.google.firebase.auth.AuthResult")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:11.8.0")] + extern(android) + internal class ValidateUser : Promise + { + + public ValidateUser(string code) + { + Init(code); + } + + [Foreign(Language.Java)] + public void Init(string code) + @{ + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(com.fuse.Activity.getRootActivity()); + String mVerificationId = preferences.getString("authVerificationID", "Error"); + if (mVerificationId.equals("Error")) { + @{ValidateUser:Of(_this).Reject(string):Call("Firebase failed to create user")}; + return; + } + PhoneAuthCredential credential = PhoneAuthProvider.getCredential(mVerificationId, code); + FirebaseAuth.getInstance().signInWithCredential(credential) + .addOnCompleteListener(com.fuse.Activity.getRootActivity(), new OnCompleteListener() { + public void onComplete(Task task) + { + if (task.isSuccessful()) + @{ValidateUser:Of(_this).Resolve(string):Call(task.getResult().getUser().getIdToken(false).getResult().getToken())}; + else + @{ValidateUser:Of(_this).Reject(string):Call(task.getException().getLocalizedMessage())}; + } + }); + @} + + void Resolve(string message) + { + AuthService.AnnounceSignIn(AuthProviderName.Phone); + base.Resolve(message); + } + + void Reject(string reason) + { + AuthService.SignalError(-1, reason); + Reject(new Exception(reason)); + } + + } + + [ForeignInclude(Language.Java, "java.util.ArrayList", "java.util.List", "android.content.Intent", + "java.util.concurrent.TimeUnit", + "android.content.SharedPreferences", + "android.util.Log", + "android.preference.PreferenceManager", + "com.google.android.gms.tasks.OnCompleteListener", + "com.google.android.gms.tasks.Task", + "com.google.firebase.auth.PhoneAuthProvider", + "com.google.firebase.auth.PhoneAuthCredential", + "com.google.firebase.auth.FirebaseAuth", + "com.google.firebase.auth.AuthResult", + "com.google.firebase.FirebaseException", + "com.google.firebase.FirebaseTooManyRequestsException", + "com.google.firebase.auth.FirebaseAuthInvalidCredentialsException")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:11.8.0")] + extern(android) + internal class ResendCodeValidation : Promise + { + + public ResendCodeValidation(string phone) + { + Init(phone); + } + + [Foreign(Language.Java)] + public void Init(string phone) + @{ + PhoneAuthProvider.OnVerificationStateChangedCallbacks mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { + + @Override + public void onVerificationCompleted(PhoneAuthCredential credential) { + // This callback will be invoked in two situations: + // 1 - Instant verification. In some cases the phone number can be instantly + // verified without needing to send or enter a verification code. + // 2 - Auto-retrieval. On some devices Google Play services can automatically + // detect the incoming verification SMS and perform verification without + // user action. + Log.d("Log ", "onVerificationCompleted:" + credential); + FirebaseAuth.getInstance().signInWithCredential(credential) + .addOnCompleteListener(com.fuse.Activity.getRootActivity(), new OnCompleteListener() { + public void onComplete(Task task) { + if (task.isSuccessful()) { + @{ResendCodeValidation:Of(_this).Auth(string):Call(task.getResult().getUser().getIdToken(false).getResult().getToken())}; + } + else { + Log.d("Log ", " " + task.getException().toString()); + @{ResendCodeValidation:Of(_this).Reject(string):Call(task.getException().getLocalizedMessage())}; + } + }}); + } + + @Override + public void onVerificationFailed(FirebaseException e) { + // This callback is invoked in an invalid request for verification is made, + // for instance if the the phone number format is not valid. + Log.w("Log ", "onVerificationFailed", e); + + if (e instanceof FirebaseAuthInvalidCredentialsException) { + // Invalid request + // ... + @{CreateUser:Of(_this).Reject(string):Call("The country code or mobile number may be incorrect")}; + return; + } else if (e instanceof FirebaseTooManyRequestsException) { + // The SMS quota for the project has been exceeded + // ... + } + + @{ResendCodeValidation:Of(_this).Reject(string):Call("SMS failed")}; + + // Show a message and update the UI + // ... + } + + @Override + public void onCodeSent(String verificationId, + PhoneAuthProvider.ForceResendingToken token) { + // The SMS verification code has been sent to the provided phone number, we + // now need to ask the user to enter the code and then construct a credential + // by combining the code with a verification ID. + Log.d("Log ", "onCodeSent:" + verificationId); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(com.fuse.Activity.getRootActivity()); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("authVerificationID", verificationId); + editor.apply(); + @{Firebase.Authentication.Phone.JS.PhoneModule.CodeSent(string):Call("Code sent")}; + } + }; + + PhoneAuthProvider.ForceResendingToken mResendToken = (PhoneAuthProvider.ForceResendingToken)@{Firebase.Authentication.Phone.CreateUser.mResendToken}; + PhoneAuthProvider.getInstance().verifyPhoneNumber( + phone, // Phone number to verify + 60, // Timeout duration + TimeUnit.SECONDS, // Unit of timeout + com.fuse.Activity.getRootActivity(), // Activity (for callback binding) + mCallbacks, + mResendToken); + @} + + void Resolve(string message) + { + base.Resolve(message); + } + + void Reject(string reason) + { + AuthService.SignalError(-1, reason); + Reject(new Exception(reason)); + } + + void Auth(string message) { + AuthService.AnnounceSignIn(AuthProviderName.Phone); + base.Resolve(message); + } + + } + + extern(android) + internal class ReAuthenticate : Promise + { + + [Foreign(Language.Java)] + public ReAuthenticate(string phone) + @{ + + @} + + void Reject(string reason) + { + Reject(new Exception(reason)); + } + + } +} diff --git a/src/Firebase.Authentication.Phone/Authentication.uno b/src/Firebase.Authentication.Phone/Authentication.uno new file mode 100644 index 0000000..aa18157 --- /dev/null +++ b/src/Firebase.Authentication.Phone/Authentication.uno @@ -0,0 +1,60 @@ +using Uno; +using Uno.UX; +using Uno.Collections; +using Uno.Compiler.ExportTargetInterop; +using Fuse; +using Fuse.Triggers; +using Fuse.Controls; +using Fuse.Controls.Native; +using Fuse.Controls.Native.Android; +using Uno.Threading; +using Firebase.Authentication; + +namespace Firebase.Authentication.Phone +{ + public class PhoneService : Firebase.Authentication.AuthProvider + { + static bool _initd = false; + + internal static void Init() + { + if (_initd) return; + var es = new PhoneService(); + Firebase.Authentication.AuthService.RegisterAuthProvider(es); + _initd = true; + } + + public override AuthProviderName Name { get { return AuthProviderName.Phone; } } + + public override void Start() {} + + public override void SignOut() {} + + public override Promise ReAuthenticate(string phone, string password) + { + return new ReAuthenticate(phone); + } + } + + extern(!mobile) + internal class CreateUser : Promise + { + public CreateUser(string phone) { } + public void Reject(string reason) { } + } + + extern(!mobile) + internal class ValidateUser : Promise + { + public ValidateUser(string code) { } + public void Reject(string reason) { } + } + + extern(!mobile) + internal class ReAuthenticate : Promise + { + public ReAuthenticate(string phone ) { } + public void Reject(string reason) { } + } + +} diff --git a/src/Firebase.Authentication.Phone/Firebase.Authentication.Phone.unoproj b/src/Firebase.Authentication.Phone/Firebase.Authentication.Phone.unoproj new file mode 100644 index 0000000..d14fef5 --- /dev/null +++ b/src/Firebase.Authentication.Phone/Firebase.Authentication.Phone.unoproj @@ -0,0 +1,14 @@ +{ + "RootNamespace":"", + "Packages": [ + "Fuse", + "FuseJS" + ], + "Projects": [ + "../Firebase.Authentication/Firebase.Authentication.unoproj" + ], + "Includes": [ + "*" + ] +} + diff --git a/src/Firebase.Authentication.Phone/JS.uno b/src/Firebase.Authentication.Phone/JS.uno new file mode 100644 index 0000000..ec090a3 --- /dev/null +++ b/src/Firebase.Authentication.Phone/JS.uno @@ -0,0 +1,67 @@ +using Uno; +using Uno.UX; +using Uno.Threading; +using Uno.Text; +using Uno.Platform; +using Uno.Compiler.ExportTargetInterop; +using Uno.Collections; +using Fuse; +using Fuse.Scripting; +using Fuse.Reactive; + +namespace Firebase.Authentication.Phone.JS +{ + /** + */ + [UXGlobalModule] + public sealed class PhoneModule : NativeModule + { + static readonly PhoneModule _instance; + static NativeEvent _onCodeSent; + + public PhoneModule() + { + if(_instance != null) return; + Uno.UX.Resource.SetGlobalKey(_instance = this, "Firebase/Authentication/Phone"); + + Firebase.Authentication.Phone.PhoneService.Init(); + _onCodeSent = new NativeEvent("onCodeSentEvent"); + AddMember(_onCodeSent); + + AddMember(new NativePromise("createUserWithPhoneNumber", CreateUserWithPhoneNumber)); + AddMember(new NativePromise("validateUserWithCode", ValidateUserWithCode)); + AddMember(new NativePromise("resendCodeValidation", ResendCodeValidation)); + } + + Future CreateUserWithPhoneNumber(object[] args) + { + var phone = (string)args[0]; + return new Firebase.Authentication.Phone.CreateUser(phone); + } + + Future ValidateUserWithCode(object[] args) + { + var code = (string)args[0]; + return new Firebase.Authentication.Phone.ValidateUser(code); + } + + Future ResendCodeValidation(object[] args) + { + var phone = (string)args[0]; + if defined(Android) + { + return new Firebase.Authentication.Phone.ResendCodeValidation(phone); + } else if defined(iOS) { + return new Firebase.Authentication.Phone.CreateUser(phone); + } else { + return null; + } + } + + static void CodeSent(string token) + { + _onCodeSent.RaiseAsync(_onCodeSent.ThreadWorker, token); + } + + } +} diff --git a/src/Firebase.Authentication.Phone/iOSImpl.uno b/src/Firebase.Authentication.Phone/iOSImpl.uno new file mode 100644 index 0000000..22c7c62 --- /dev/null +++ b/src/Firebase.Authentication.Phone/iOSImpl.uno @@ -0,0 +1,129 @@ +using Uno; +using Uno.UX; +using Uno.Collections; +using Uno.Compiler.ExportTargetInterop; +using Fuse; +using Fuse.Triggers; +using Fuse.Controls; +using Fuse.Controls.Native; +using Fuse.Controls.Native.Android; +using Uno.Threading; +using Firebase.Authentication; + +namespace Firebase.Authentication.Phone +{ + [Require("AppDelegate.SourceFile.Declaration", "#include ")] + [Require("AppDelegate.SourceFile.Declaration", "#include ")] + [Require("Source.Include", "Firebase/Firebase.h")] + [Require("Source.Include", "FirebaseAuth/FirebaseAuth.h")] + [Require("Cocoapods.Podfile.Target", "pod 'Firebase/Auth'")] + extern(iOS) + class CreateUser : Promise + { + [Foreign(Language.ObjC)] + public CreateUser(string phone) + @{ + [[FIRPhoneAuthProvider provider] + verifyPhoneNumber:phone + UIDelegate:nil + completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { + if (error) { + if (error.code == FIRAuthErrorCodeInvalidPhoneNumber) { + @{CreateUser:Of(_this).Reject(string):Call(@"The country code or mobile number may be incorrect")}; + }else { + NSLog(@"%@", error.localizedDescription); + @{CreateUser:Of(_this).Reject(string):Call(error.localizedDescription)}; + } + } + else { + // Sign in using the verificationID and the code sent to the user + // ... + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setObject:verificationID forKey:@"authVerificationID"]; + NSLog(@"%@", verificationID); + @{CreateUser:Of(_this).Resolve(string):Call(@"Code sent")}; + } + }]; + @} + + void Resolve(string message) + { + base.Resolve(message); + } + + void Reject(string reason) + { + AuthService.SignalError(-1, reason); + Reject(new Exception(reason)); + } + } + + [Require("Source.Include", "Firebase/Firebase.h")] + [Require("Source.Include", "FirebaseAuth/FirebaseAuth.h")] + [Require("Cocoapods.Podfile.Target", "pod 'Firebase/Auth'")] + extern(iOS) + class ValidateUser : Promise + { + [Foreign(Language.ObjC)] + public ValidateUser(string code) + @{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSString *verificationID = [defaults stringForKey:@"authVerificationID"]; + NSLog(@"%@", verificationID); + FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider] + credentialWithVerificationID:verificationID + verificationCode:code]; + [[FIRAuth auth] + signInWithCredential:credential + completion:^(FIRUser *user, NSError *error) { + if (error) { + NSLog(@"%@", error.localizedDescription); + @{ValidateUser:Of(_this).Reject(string):Call(error.localizedDescription)}; + } + else { + [user getIDTokenWithCompletion:^(NSString *idToken, NSError* fidError) { + if (fidError) { + @{ValidateUser:Of(_this).Reject(string):Call(fidError.localizedDescription)}; + } + else { + @{ValidateUser:Of(_this).Resolve(string):Call(idToken)}; + } + }]; + } + }]; + @} + + void Resolve(string message) + { + AuthService.AnnounceSignIn(AuthProviderName.Phone); + base.Resolve(message); + } + + void Reject(string reason) + { + AuthService.SignalError(-1, reason); + Reject(new Exception(reason)); + } + } + + [Require("Source.Include", "Firebase/Firebase.h")] + [Require("Source.Include", "FirebaseAuth/FirebaseAuth.h")] + [Require("Cocoapods.Podfile.Target", "pod 'Firebase/Auth'")] + [Require("Source.Include", "@{Firebase.Authentication.User:Include}")] + extern(iOS) + class ReAuthenticate : Promise + { + [Foreign(Language.ObjC)] + public ReAuthenticate(string code) + @{ + + @} + + void Reject(string reason) + { + Reject(new Exception(reason)); + } + + } + +} diff --git a/src/Firebase.Authentication.Phone/iOSImpl.uxl b/src/Firebase.Authentication.Phone/iOSImpl.uxl new file mode 100644 index 0000000..cdd8fa1 --- /dev/null +++ b/src/Firebase.Authentication.Phone/iOSImpl.uxl @@ -0,0 +1,7 @@ + + + + + diff --git a/src/Firebase.Authentication/Authentication.uno b/src/Firebase.Authentication/Authentication.uno index 6ac297c..5878796 100644 --- a/src/Firebase.Authentication/Authentication.uno +++ b/src/Firebase.Authentication/Authentication.uno @@ -12,7 +12,7 @@ using Uno.Threading; namespace Firebase.Authentication { // this enum is a shame but we want to talk about the backend even when it isnt included - public enum AuthProviderName { None, Email, Facebook, Google } + public enum AuthProviderName { None, Email, Facebook, Google, Phone } [ForeignInclude(Language.Java, "java.util.ArrayList", "java.util.List", "android.graphics.Color", "com.google.android.gms.tasks.OnCompleteListener", @@ -20,7 +20,7 @@ namespace Firebase.Authentication "com.google.firebase.auth.AuthResult", "com.google.firebase.auth.FirebaseAuth", "com.google.firebase.auth.FirebaseUser")] - [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:9.2.0")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-auth:11.8.0")] [Require("AppDelegate.SourceFile.Declaration", "#include <@{Firebase.Authentication.AuthService:Include}>")] [extern(iOS) Require("Source.Include", "Firebase/Firebase.h")] [extern(iOS) Require("Source.Include", "FirebaseAuth/FirebaseAuth.h")] diff --git a/src/Firebase.Authentication/Firebase.Authentication.unoproj b/src/Firebase.Authentication/Firebase.Authentication.unoproj index 97ac26d..2babcec 100644 --- a/src/Firebase.Authentication/Firebase.Authentication.unoproj +++ b/src/Firebase.Authentication/Firebase.Authentication.unoproj @@ -12,7 +12,8 @@ "Firebase.Authentication.Google", "Firebase.Authentication.Facebook", "Firebase.Authentication.Twitter", - "Firebase.Authentication.Github" + "Firebase.Authentication.Github", + "Firebase.Authentication.Phone" ], "Includes": [ "*" diff --git a/src/Firebase.Authentication/JS.uno b/src/Firebase.Authentication/JS.uno index 996b962..520cb9f 100644 --- a/src/Firebase.Authentication/JS.uno +++ b/src/Firebase.Authentication/JS.uno @@ -103,18 +103,16 @@ namespace Firebase.Authentication.JS static void OnUser() { var isSignedIn = GetSignedIn(); - _onSignInChanged.RaiseAsync(isSignedIn); _instance.Emit("signedInStateChanged", isSignedIn); } static void OnError(int errorCode, string message) { - _onError.RaiseAsync(message, errorCode); + //_onError.RaiseAsync(message, errorCode); + _onError.RaiseAsync(_onError.ThreadWorker, message, errorCode); _instance.Emit("error", message, errorCode); } - - // functions static Future GetToken(object[] arg) { diff --git a/src/Firebase.Authentication/iOSImpl.uno b/src/Firebase.Authentication/iOSImpl.uno index 56491bf..bd5271a 100644 --- a/src/Firebase.Authentication/iOSImpl.uno +++ b/src/Firebase.Authentication/iOSImpl.uno @@ -64,10 +64,8 @@ namespace Firebase.Authentication [Foreign(Language.ObjC)] public GetToken() @{ - NSLog(@"We are in obj-c trying to get token"); FIRUser* user = [FIRAuth auth].currentUser; - [user getTokenWithCompletion: ^(NSString *_Nullable token, NSError *_Nullable error) { - NSLog(@"%@", token); + [user getIDTokenWithCompletion: ^(NSString *_Nullable token, NSError *_Nullable error) { if (error) { @{GetToken:Of(_this).Reject(string):Call(@"failed getting token for user")}; } else { diff --git a/src/Firebase.Database/Database.uno b/src/Firebase.Database/Database.uno index 21234e2..9d428df 100644 --- a/src/Firebase.Database/Database.uno +++ b/src/Firebase.Database/Database.uno @@ -18,11 +18,16 @@ namespace Firebase.Database "com.google.firebase.database.DataSnapshot", "com.google.firebase.database.FirebaseDatabase", "com.google.firebase.database.ValueEventListener", + "com.google.firebase.database.ChildEventListener", + "com.google.firebase.database.Query", + "com.google.firebase.database.ServerValue", + "android.util.Log", "org.json.JSONObject", "java.util.Map", - "java.util.HashMap")] + "java.util.HashMap", + "java.util.ArrayList")] [Require("Cocoapods.Podfile.Target", "pod 'Firebase/Database'")] - [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-database:9.2.0")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-database:11.8.0")] [extern(iOS) Require("Source.Import","FirebaseDatabase/FIRDatabase.h")] extern(mobile) static class DatabaseService @@ -30,6 +35,10 @@ namespace Firebase.Database static bool _initialized; extern(android) static Java.Object _handle; extern(ios) public static ObjC.Object _handle; + extern(android) static Java.Object _valueListenerMap; // For storing ChildEventListener + extern(android) static Java.Object _childListenerMap; // For storing ValueEventListener + extern(android) static Java.Object _queryListenerArray; // For storing Query EventListener + public static void Init() { @@ -56,6 +65,401 @@ namespace Firebase.Database public static void AndroidInit() @{ @{_handle:Set(FirebaseDatabase.getInstance().getReference())}; + + // Initialize variables + @{_childListenerMap:Set(new HashMap())}; + @{_valueListenerMap:Set(new HashMap())}; + @{_queryListenerArray:Set(new ArrayList())}; + @} + + [Foreign(Language.ObjC)] + extern(iOS) + public static void ListenForChildAdded(string path, int count, Action f) + @{ + NSUInteger end = (NSUInteger) count; + FIRDatabaseReference *ref = @{DatabaseService._handle:Get()}; + [[[ref child:path] queryLimitedToLast:count] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { + if([snapshot.value isEqual:[NSNull null]]) { + f(path, nil); + return; + } + + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:snapshot.value + options:(NSJSONWritingOptions)0 + error:&error]; + NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + f(path, json); + } withCancelBlock:^(NSError * _Nonnull error) { + NSString *erstr = [NSString stringWithFormat:@"Firebase Read Error: %@", error.localizedDescription]; + f(path, erstr); + }]; + @} + + [Foreign(Language.Java)] + extern(Android) + public static void ListenForChildAdded(string path, int count, Action f) + @{ + ChildEventListener childEventListener = new ChildEventListener() { + @Override + public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { + if (dataSnapshot.getValue() == null) { + return; + } + try { + JSONObject json = new JSONObject((Map)dataSnapshot.getValue()); + f.run(path,json.toString()); + } catch (Throwable t) { + Log.e("Error ", "Could not Parse JSON: \"" + dataSnapshot.getValue() + "\""); + } + } + + @Override + public void onChildChanged(DataSnapshot dataSnapshot, String s) { + + } + + @Override + public void onChildRemoved(DataSnapshot dataSnapshot) { + + } + + @Override + public void onChildMoved(DataSnapshot dataSnapshot, String s) { + + } + + @Override + public void onCancelled(DatabaseError databaseError) { + f.run(path,databaseError.toString()); + } + }; + DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; + Query lastAddedQuery = ref.child(path).limitToLast(count); + lastAddedQuery.addChildEventListener(childEventListener); + + @SuppressWarnings("unchecked") // For removing cast check warnings + ArrayList queryListenerArray = (ArrayList)@{DatabaseService._queryListenerArray:Get()}; + FirebaseQueryObject firebaseQueryObject = new FirebaseQueryObject(path, lastAddedQuery, childEventListener); + queryListenerArray.add(firebaseQueryObject); // Add firebaseQueryObject to query array + @} + + [Foreign(Language.ObjC)] + extern(iOS) + public static void ReadByQueryEndingAtValue(string path, string keyName, string lastValue, int count, Action f) + @{ + long longLastValue = [lastValue longLongValue]; + NSNumber * lastChildValue = [NSNumber numberWithLong:longLastValue]; + FIRDatabaseReference *ref = @{DatabaseService._handle:Get()}; + [[[[[ref child:path] queryOrderedByChild:keyName] queryEndingAtValue:lastChildValue] queryLimitedToLast:count] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { + if([snapshot.value isEqual:[NSNull null]]) { + f(path, nil); + return; + } + + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:snapshot.value + options:(NSJSONWritingOptions)0 + error:&error]; + NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + f(path, json); + } withCancelBlock:^(NSError * _Nonnull error) { + NSString *erstr = [NSString stringWithFormat:@"Firebase Read Error: %@", error.localizedDescription]; + f(path, erstr); + }]; + @} + + [Foreign(Language.Java)] + extern(Android) + public static void ReadByQueryEndingAtValue(string path, string keyName, string lastValue, int count, Action f) + @{ + ValueEventListener dataListener = new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) + { + if (dataSnapshot.getValue() == null) { + return; + } + try { + JSONObject json = new JSONObject((Map)dataSnapshot.getValue()); + f.run(path,json.toString()); + } catch (Throwable t) { + Log.e("Error ", "Could not Parse JSON: \"" + dataSnapshot.getValue() + "\""); + } + } + + @Override + public void onCancelled(DatabaseError databaseError) + { + f.run(path,databaseError.toString()); + } + }; + + long longLastValue = Long.parseLong(lastValue); + DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; + Query readQuery = ref.child(path).orderByChild(keyName).endAt(longLastValue).limitToLast(count); + readQuery.addValueEventListener(dataListener); + + @SuppressWarnings("unchecked") + ArrayList queryListenerArray = (ArrayList)@{DatabaseService._queryListenerArray:Get()}; + FirebaseQueryObject firebaseQueryObject = new FirebaseQueryObject(path, readQuery, dataListener); + queryListenerArray.add(firebaseQueryObject); + @} + + [Foreign(Language.ObjC)] + extern(iOS) + public static void ListenForChildChanged(string path, Action f) + @{ + FIRDatabaseReference *ref = @{DatabaseService._handle:Get()}; + [[ref child:path] observeEventType:FIRDataEventTypeChildChanged withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { + if([snapshot.value isEqual:[NSNull null]]) { + f(path, nil); + return; + } + + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:snapshot.value + options:(NSJSONWritingOptions)0 + error:&error]; + NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + f(path, json); + } withCancelBlock:^(NSError * _Nonnull error) { + NSString *erstr = [NSString stringWithFormat:@"Firebase Read Error: %@", error.localizedDescription]; + f(path, erstr); + }]; + @} + + [Foreign(Language.Java)] + extern(Android) + public static void ListenForChildChanged(string path, Action f) + @{ + ChildEventListener childEventListener = new ChildEventListener() { + @Override + public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { + + } + + @Override + public void onChildChanged(DataSnapshot dataSnapshot, String s) { + if (dataSnapshot.getValue() == null) { + return; + } + try { + JSONObject json = new JSONObject((Map)dataSnapshot.getValue()); + f.run(path,json.toString()); + } catch (Throwable t) { + Log.e("Error ", "Could not Parse JSON: \"" + dataSnapshot.getValue() + "\""); + } + } + + @Override + public void onChildRemoved(DataSnapshot dataSnapshot) { + + } + + @Override + public void onChildMoved(DataSnapshot dataSnapshot, String s) { + + } + + @Override + public void onCancelled(DatabaseError databaseError) { + f.run(path,databaseError.toString()); + } + }; + DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; + ref.child(path).addChildEventListener(childEventListener); + @SuppressWarnings("unchecked") + HashMap mListenerMap = (HashMap)@{DatabaseService._childListenerMap:Get()}; + mListenerMap.put(path, childEventListener); // Add to Child event listener map + @} + + [Foreign(Language.ObjC)] + extern(iOS) + public static void ListenForChildRemoved(string path, Action f) + @{ + FIRDatabaseReference *ref = @{DatabaseService._handle:Get()}; + [[ref child:path] observeEventType:FIRDataEventTypeChildRemoved withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { + if([snapshot.value isEqual:[NSNull null]]) { + f(path, nil); + return; + } + + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:snapshot.value + options:(NSJSONWritingOptions)0 + error:&error]; + NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + f(path, json); + } withCancelBlock:^(NSError * _Nonnull error) { + NSString *erstr = [NSString stringWithFormat:@"Firebase Read Error: %@", error.localizedDescription]; + f(path, erstr); + }]; + @} + + [Foreign(Language.Java)] + extern(Android) + public static void ListenForChildRemoved(string path, Action f) + @{ + ChildEventListener childEventListener = new ChildEventListener() { + @Override + public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { + + } + + @Override + public void onChildChanged(DataSnapshot dataSnapshot, String s) { + + } + + @Override + public void onChildRemoved(DataSnapshot dataSnapshot) { + if (dataSnapshot.getValue() == null) { + return; + } + try { + JSONObject json = new JSONObject((Map)dataSnapshot.getValue()); + f.run(path,json.toString()); + } catch (Throwable t) { + Log.e("Error ", "Could not Parse JSON: \"" + dataSnapshot.getValue() + "\""); + } + } + + @Override + public void onChildMoved(DataSnapshot dataSnapshot, String s) { + + } + + @Override + public void onCancelled(DatabaseError databaseError) { + f.run(path,databaseError.toString()); + } + }; + DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; + ref.child(path).addChildEventListener(childEventListener); + @SuppressWarnings("unchecked") + HashMap mListenerMap = (HashMap)@{DatabaseService._childListenerMap:Get()}; + mListenerMap.put(path, childEventListener); + @} + + [Foreign(Language.ObjC)] + extern(iOS) + public static void ListenForChildMoved(string path, Action f) + @{ + FIRDatabaseReference *ref = @{DatabaseService._handle:Get()}; + [[ref child:path] observeEventType:FIRDataEventTypeChildMoved withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { + if([snapshot.value isEqual:[NSNull null]]) { + f(path, nil); + return; + } + + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:snapshot.value + options:(NSJSONWritingOptions)0 + error:&error]; + NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + f(path, json); + } withCancelBlock:^(NSError * _Nonnull error) { + NSString *erstr = [NSString stringWithFormat:@"Firebase Read Error: %@", error.localizedDescription]; + f(path, erstr); + }]; + @} + + [Foreign(Language.Java)] + extern(Android) + public static void ListenForChildMoved(string path, Action f) + @{ + ChildEventListener childEventListener = new ChildEventListener() { + @Override + public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { + + } + + @Override + public void onChildChanged(DataSnapshot dataSnapshot, String s) { + + } + + @Override + public void onChildRemoved(DataSnapshot dataSnapshot) { + + } + + @Override + public void onChildMoved(DataSnapshot dataSnapshot, String s) { + if (dataSnapshot.getValue() == null) { + return; + } + try { + JSONObject json = new JSONObject((Map)dataSnapshot.getValue()); + f.run(path,json.toString()); + } catch (Throwable t) { + Log.e("Error ", "Could not Parse JSON: \"" + dataSnapshot.getValue() + "\""); + } + } + + @Override + public void onCancelled(DatabaseError databaseError) { + f.run(path,databaseError.toString()); + } + }; + DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; + ref.child(path).addChildEventListener(childEventListener); + @SuppressWarnings("unchecked") + HashMap mListenerMap = (HashMap)@{DatabaseService._childListenerMap:Get()}; + mListenerMap.put(path, childEventListener); + @} + + [Foreign(Language.ObjC)] + extern(iOS) + public static void DetachListeners(string path) + @{ + FIRDatabaseReference *ref = @{DatabaseService._handle:Get()}; + [[ref child:path] removeAllObservers]; + @} + + [Foreign(Language.Java)] + extern(Android) + public static void DetachListeners(string path) + @{ + DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; + + // Remove ChildEventListener if any set with this path + @SuppressWarnings("unchecked") + HashMap childListenerMap = (HashMap)@{DatabaseService._childListenerMap:Get()}; + for (Map.Entry entry : childListenerMap.entrySet()) { + String pathString = entry.getKey(); + if (pathString.equals(path)) { // Check for path name + ChildEventListener listener = entry.getValue(); + ref.child(path).removeEventListener(listener); + childListenerMap.remove(entry); + } + } + + // Remove ValueEventListener if any set with this path + @SuppressWarnings("unchecked") + HashMap valueListenerMap = (HashMap)@{DatabaseService._valueListenerMap:Get()}; + for (Map.Entry entry : valueListenerMap.entrySet()) { + String pathString = entry.getKey(); + if (pathString.equals(path)) { + ValueEventListener listener = entry.getValue(); + ref.child(path).removeEventListener(listener); + valueListenerMap.remove(entry); + } + } + + // Remove Query ValueEventListener if any set with this path + @SuppressWarnings("unchecked") + ArrayList queryListenerArray = (ArrayList)@{DatabaseService._queryListenerArray:Get()}; + for (FirebaseQueryObject firebaseQueryObject : queryListenerArray) { + if (firebaseQueryObject.getPath().equals(path)) { + if (firebaseQueryObject.getChildEventListener() != null) + firebaseQueryObject.getQuery().removeEventListener(firebaseQueryObject.getChildEventListener()); + + if (firebaseQueryObject.getValueEventListener() != null) + firebaseQueryObject.getQuery().removeEventListener(firebaseQueryObject.getValueEventListener()); + } + } @} [Foreign(Language.ObjC)] @@ -89,8 +493,12 @@ namespace Firebase.Database @Override public void onDataChange(DataSnapshot dataSnapshot) { - JSONObject json = new JSONObject((Map)dataSnapshot.getValue()); - f.run(path,json.toString()); + try { + JSONObject json = new JSONObject((Map)dataSnapshot.getValue()); + f.run(path,json.toString()); + } catch (Throwable t) { + Log.e("Error ", "Could not Parse JSON: \"" + dataSnapshot.getValue() + "\""); + } } @Override @@ -101,6 +509,9 @@ namespace Firebase.Database }; DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; ref.child(path).addValueEventListener(dataListener); + @SuppressWarnings("unchecked") + HashMap mListenerMap = (HashMap)@{DatabaseService._valueListenerMap:Get()}; + mListenerMap.put(path, dataListener); // Add to Value event listener Map @} @@ -144,6 +555,35 @@ namespace Firebase.Database ref.child(path).setValue(values); @} + [Foreign(Language.ObjC)] + extern(iOS) + public static void SaveWithTimestamp(string path, ObjC.Object value) + @{ + FIRDatabaseReference *ref = @{DatabaseService._handle:Get()}; + NSMutableDictionary *messageDic = [[NSMutableDictionary alloc] initWithDictionary:value]; + [messageDic setValue:[FIRServerValue timestamp] forKey:@"timestamp"]; + [[ref child:path] setValue:messageDic]; + @} + + [Foreign(Language.Java)] + extern(Android) + public static void SaveWithTimestamp(string path, Java.Object value) + @{ + DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; + try { + Map obj = (Map) value; + if (obj != null) { + obj.put("timestamp", ServerValue.TIMESTAMP); + ref.child(path).setValue(obj); + } + else { + ref.child(path).setValue(value); + } + } catch (Throwable t) { + Log.e("Error ", "Could not Save message: \"" + value.toString() + "\""); + } + @} + [Foreign(Language.ObjC)] extern(iOS) public static void Save(string path, ObjC.Object value) @@ -232,11 +672,9 @@ namespace Firebase.Database public static void Init() {} public static string NewChildId(string path) { - debug_log "NewChildId not implemented for desktop"; return "unknown"; } public static void Save() { - debug_log "Save not implemented for desktop"; } public static void Save(string path, string[] keys, string[] vals, int len) { @@ -259,9 +697,44 @@ namespace Firebase.Database Save(); } + public static void SaveWithTimestamp(string path, Object value) + { + Save(); + } + public static void Listen(string path, Action f) { - debug_log "Listen not implemented for desktop"; + + } + + public static void ReadByQueryEndingAtValue(string path, string keyName, string lastValue, int count, Action f) + { + + } + + public static void ListenForChildAdded(string path, int count, Action f) + { + + } + + public static void ListenForChildChanged(string path, Action f) + { + + } + + public static void ListenForChildRemoved(string path, Action f) + { + + } + + public static void ListenForChildMoved(string path, Action f) + { + + } + + public static void DetachListeners(string path) + { + } } @@ -274,6 +747,15 @@ namespace Firebase.Database } } + extern(!mobile) + internal class ReadByQueryEqualToValue : Promise + { + public ReadByQueryEqualToValue(string path, string key, object val) + { + Reject(new Exception("Not implemented on desktop")); + } + } + [Require("Entity", "DatabaseService")] [Require("Source.Import","FirebaseDatabase/FIRDatabase.h")] [Require("Source.Include","@{DatabaseService:Include}")] @@ -305,6 +787,38 @@ namespace Firebase.Database void Reject(string reason) { Reject(new Exception(reason)); } } + [Require("Entity", "DatabaseService")] + [Require("Source.Import","FirebaseDatabase/FIRDatabase.h")] + [Require("Source.Include","@{DatabaseService:Include}")] + extern(iOS) + internal class ReadByQueryEqualToValue : Promise + { + [Foreign(Language.ObjC)] + public ReadByQueryEqualToValue(string path, string key, ObjC.Object val) + @{ + FIRDatabaseReference *ref = @{DatabaseService._handle:Get()}; + + [[[[ref child:path] queryOrderedByChild:key] queryEqualToValue:val] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { + NSError *error; + + @{ReadByQueryEqualToValue:Of(_this).Resolve(string):Call(( + ([snapshot.value isEqual:[NSNull null]]) + ? nil + : ([snapshot.value isKindOfClass:[NSString class]]) + ? [NSString stringWithFormat:@"\"%@\"", snapshot.value] + : ([snapshot.value isKindOfClass:[NSNumber class]]) + ? [NSString stringWithFormat:@"%@", snapshot.value] + : [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:snapshot.value options:(NSJSONWritingOptions)0 error:&error] encoding:NSUTF8StringEncoding] + ))}; + + } withCancelBlock:^(NSError * _Nonnull error) { + NSString *erstr = [NSString stringWithFormat:@"Firebase Read Error: %@", error.localizedDescription]; + @{ReadByQueryEqualToValue:Of(_this).Reject(string):Call(erstr)}; + }]; + @} + void Reject(string reason) { Reject(new Exception(reason)); } + } + [ForeignInclude(Language.Java, "com.google.firebase.database.DatabaseReference", "com.google.firebase.database.DatabaseError", @@ -314,6 +828,7 @@ namespace Firebase.Database "org.json.JSONObject", "org.json.JSONArray", "java.util.Map", + "java.util.HashMap", "java.util.List")] extern(Android) internal class Read : Promise @@ -347,6 +862,64 @@ namespace Firebase.Database }; DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; ref.child(path).addListenerForSingleValueEvent(dataListener); + @SuppressWarnings("unchecked") + HashMap mListenerMap = (HashMap)@{DatabaseService._valueListenerMap:Get()}; + mListenerMap.put(path, dataListener); + @} + void Reject(string reason) { Reject(new Exception(reason)); } + } + + [ForeignInclude(Language.Java, + "com.google.firebase.database.DatabaseReference", + "com.google.firebase.database.DatabaseError", + "com.google.firebase.database.DatabaseReference", + "com.google.firebase.database.DataSnapshot", + "com.google.firebase.database.ValueEventListener", + "org.json.JSONObject", + "org.json.JSONArray", + "java.util.Map", + "java.util.List")] + extern(Android) + internal class ReadByQueryEqualToValue : Promise + { + [Foreign(Language.Java)] + public ReadByQueryEqualToValue(string path, string key, Java.Object val) + @{ + ValueEventListener dataListener = new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) + { + Object snapshotValue = dataSnapshot.getValue(); + @{ReadByQueryEqualToValue:Of(_this).Resolve(string):Call(( + (snapshotValue == null) + ? null + : (snapshotValue instanceof Map) + ? new JSONObject((Map) snapshotValue).toString() + : (snapshotValue instanceof List) + ? new JSONArray((List) snapshotValue).toString() + : (snapshotValue instanceof String) + ? "\"" + snapshotValue.toString() + "\"" + : snapshotValue.toString() + ))}; + } + + @Override + public void onCancelled(DatabaseError databaseError) + { + @{ReadByQueryEqualToValue:Of(_this).Reject(string):Call(databaseError.toString())}; + } + }; + DatabaseReference ref = (DatabaseReference)@{DatabaseService._handle:Get()}; + if(val instanceof String){ + String obj = (String) val; + ref.child(path).orderByChild(key).equalTo(obj).addListenerForSingleValueEvent(dataListener); + } else if (val instanceof Double){ + Double obj = (Double) val; + ref.child(path).orderByChild(key).equalTo(obj).addListenerForSingleValueEvent(dataListener); + } else if( val instanceof Boolean ){ + Boolean obj = (Boolean) val; + ref.child(path).orderByChild(key).equalTo(obj).addListenerForSingleValueEvent(dataListener); + } @} void Reject(string reason) { Reject(new Exception(reason)); } } diff --git a/src/Firebase.Database/Firebase.Database.unoproj b/src/Firebase.Database/Firebase.Database.unoproj index d79bbd0..2d943c0 100644 --- a/src/Firebase.Database/Firebase.Database.unoproj +++ b/src/Firebase.Database/Firebase.Database.unoproj @@ -8,6 +8,7 @@ "FuseJS" ], "Includes": [ - "*" + "*", + "FirebaseQueryObject.java:Java:Android", ] } diff --git a/src/Firebase.Database/FirebaseQueryObject.java b/src/Firebase.Database/FirebaseQueryObject.java new file mode 100644 index 0000000..9e40f6e --- /dev/null +++ b/src/Firebase.Database/FirebaseQueryObject.java @@ -0,0 +1,45 @@ +package com.foreign.Firebase.Database; + +import com.google.firebase.database.ChildEventListener; +import com.google.firebase.database.Query; +import com.google.firebase.database.ValueEventListener; + +/** + * Created by dhvlsimac on 28/03/18. + */ + +public class FirebaseQueryObject { + + private String path; + private Query query; + private ChildEventListener childEventListener; + private ValueEventListener valueEventListener; + + FirebaseQueryObject(String path, Query query, ChildEventListener listener) { + this.path = path; + this.query = query; + this.childEventListener = listener; + } + + FirebaseQueryObject(String path, Query query, ValueEventListener listener) { + this.path = path; + this.query = query; + this.valueEventListener = listener; + } + + public String getPath() { + return path; + } + + public Query getQuery() { + return query; + } + + public ChildEventListener getChildEventListener() { + return childEventListener; + } + + public ValueEventListener getValueEventListener() { + return valueEventListener; + } +} diff --git a/src/Firebase.Database/JS.uno b/src/Firebase.Database/JS.uno index 82ecf73..5d4f910 100644 --- a/src/Firebase.Database/JS.uno +++ b/src/Firebase.Database/JS.uno @@ -19,7 +19,7 @@ namespace Firebase.Database.JS { static readonly DatabaseModule _instance; - public DatabaseModule() : base(false,"data") + public DatabaseModule() : base(false,"data", "dataAdded", "dataChanged", "dataRemoved", "dataMoved", "readByQueryEndingAtValue") { if(_instance != null) return; Uno.UX.Resource.SetGlobalKey(_instance = this, "Firebase/Database"); @@ -28,10 +28,38 @@ namespace Firebase.Database.JS var onData = new NativeEvent("onData"); On("data", onData); + var onDataAdded = new NativeEvent("onDataAdded"); + On("dataAdded", onDataAdded); + + var onDataChanged = new NativeEvent("onDataChanged"); + On("dataChanged", onDataChanged); + + var onDataRemoved = new NativeEvent("onDataRemoved"); + On("dataRemoved", onDataRemoved); + + var onDataMoved = new NativeEvent("onDataMoved"); + On("dataMoved", onDataMoved); + + var onReadByQueryEndingAtValue = new NativeEvent("onReadByQueryEndingAtValue"); + On("readByQueryEndingAtValue", onReadByQueryEndingAtValue); + AddMember(onData); + AddMember(onDataAdded); + AddMember(onDataChanged); + AddMember(onDataRemoved); + AddMember(onDataMoved); + AddMember(onReadByQueryEndingAtValue); AddMember(new NativeFunction("listen", (NativeCallback)Listen)); + AddMember(new NativeFunction("listenOnAdded", (NativeCallback)ListenForChildAdded)); + AddMember(new NativeFunction("listenOnChanged", (NativeCallback)ListenForChildChanged)); + AddMember(new NativeFunction("listenOnRemoved", (NativeCallback)ListenForChildRemoved)); + AddMember(new NativeFunction("listenOnMoved", (NativeCallback)ListenForChildMoved)); + AddMember(new NativeFunction("readByQueryEndingAtValue", (NativeCallback)ReadByQueryEndingAtValue)); + AddMember(new NativeFunction("detachListeners", (NativeCallback)DetachListeners)); AddMember(new NativePromise("read", Read, null)); + AddMember(new NativePromise("readByQueryEqualToValue", ReadByQueryEqualToValue, null)); AddMember(new NativeFunction("push", (NativeCallback)Push)); + AddMember(new NativeFunction("pushWithTimestamp", (NativeCallback)PushWithTimestamp)); AddMember(new NativeFunction("save", (NativeCallback)Save)); AddMember(new NativeFunction("delete", (NativeCallback)Delete)); } @@ -42,6 +70,32 @@ namespace Firebase.Database.JS return new Read(path); } + static Future ReadByQueryEqualToValue(object[] args) + { + var path = args[0].ToString(); + var key = args[1].ToString(); + var val = args[2]; + + if defined(iOS) + { + return new ReadByQueryEqualToValue( + path, key, + JSON.ObjCObject.FromJSON(JSON.ScriptingValue.ToJSON(val)) + ); + } + else if defined(Android) + { + return new ReadByQueryEqualToValue( + path, key, + JSON.JavaObject.FromJSON(JSON.ScriptingValue.ToJSON(val)) + ); + } + else + { + return new ReadByQueryEqualToValue(path,key,val); + } + } + static void DoSave(string path, object arg) { if (arg is Fuse.Scripting.Object) @@ -90,9 +144,6 @@ namespace Firebase.Database.JS } else { - debug_log("Save: Unimplemented Javascript type"); - debug_log arg; - debug_log arg.GetType(); throw new Exception("Save: Unimplemented Javascript type"); } } @@ -125,6 +176,34 @@ namespace Firebase.Database.JS return push_path; } + static object PushWithTimestamp(Fuse.Scripting.Context context, object[] args) + { + var path = args[0].ToString(); + var push_path = DatabaseService.NewChildId(path); + if defined(iOS) + { + DatabaseService.SaveWithTimestamp( + path + "/" + push_path, + JSON.ObjCObject.FromJSON(JSON.ScriptingValue.ToJSON(args[1])) + ); + } + else if defined(Android) + { + DatabaseService.SaveWithTimestamp( + path + "/" + push_path, + JSON.JavaObject.FromJSON(JSON.ScriptingValue.ToJSON(args[1])) + ); + } + else + { + DoSave( + path + "/" + push_path, + args[1] + ); + } + return push_path; + } + static object Save(Fuse.Scripting.Context context, object[] args) { if defined(iOS) @@ -162,12 +241,82 @@ namespace Firebase.Database.JS Emit("data", path, msg); } + void ListenAddedCallback (string path, string msg) + { + Emit("dataAdded", path, msg); + } + + void ListenChangedCallback (string path, string msg) + { + Emit("dataChanged", path, msg); + } + + void ListenRemovedCallback (string path, string msg) + { + Emit("dataRemoved", path, msg); + } + + void ListenMovedCallback (string path, string msg) + { + Emit("dataMoved", path, msg); + } + + void ListenReadByQueryEndingAtValueCallback (string path, string msg) + { + Emit("readByQueryEndingAtValue", path, msg); + } + object Listen(Fuse.Scripting.Context context, object[] args) { - debug_log "listen"; var path = args[0].ToString(); DatabaseService.Listen(path, ListenCallback); return null; } + + object ListenForChildAdded(Fuse.Scripting.Context context, object[] args) + { + var path = args[0].ToString(); + int count = Marshal.ToInt(args[1]); + DatabaseService.ListenForChildAdded(path,count, ListenAddedCallback); + return null; + } + + object ListenForChildChanged(Fuse.Scripting.Context context, object[] args) + { + var path = args[0].ToString(); + DatabaseService.ListenForChildChanged(path, ListenChangedCallback); + return null; + } + + object ListenForChildRemoved(Fuse.Scripting.Context context, object[] args) + { + var path = args[0].ToString(); + DatabaseService.ListenForChildRemoved(path, ListenRemovedCallback); + return null; + } + + object ListenForChildMoved(Fuse.Scripting.Context context, object[] args) + { + var path = args[0].ToString(); + DatabaseService.ListenForChildMoved(path, ListenMovedCallback); + return null; + } + + object ReadByQueryEndingAtValue(Fuse.Scripting.Context context, object[] args) + { + var path = args[0].ToString(); + var keyName = args[1].ToString(); + var lastValue = args[2].ToString(); + int count = Marshal.ToInt(args[3]); + DatabaseService.ReadByQueryEndingAtValue(path,keyName,lastValue, count, ListenReadByQueryEndingAtValueCallback); + return null; + } + + object DetachListeners(Fuse.Scripting.Context context, object[] args) + { + var path = args[0].ToString(); + DatabaseService.DetachListeners(path); + return null; + } } } diff --git a/src/Firebase.Notifications.Android/Android/Impl.uno b/src/Firebase.Notifications.Android/Android/Impl.uno index 1ecbd8e..8be893f 100644 --- a/src/Firebase.Notifications.Android/Android/Impl.uno +++ b/src/Firebase.Notifications.Android/Android/Impl.uno @@ -23,7 +23,7 @@ namespace Firebase.Notifications "android.os.Bundle", "com.fuse.firebase.Notifications.PushNotificationReceiver", "com.google.firebase.messaging.RemoteMessage")] - [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-messaging:9.2.0")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-messaging:11.8.0")] extern(Android) internal class AndroidImpl { diff --git a/src/Firebase.Notifications.Android/Common.uno b/src/Firebase.Notifications.Android/Common.uno index 50263ad..f7e5a4b 100644 --- a/src/Firebase.Notifications.Android/Common.uno +++ b/src/Firebase.Notifications.Android/Common.uno @@ -10,6 +10,10 @@ using Uno.Threading; namespace Firebase.Notifications { [Require("Entity","Firebase.Core.Init()")] + [ForeignInclude(Language.Java, + "android.util.Log", + "com.google.firebase.iid.FirebaseInstanceId")] + [extern(iOS) Require("Source.Include", "Firebase/Firebase.h")] public static class NotificationService { extern(!Android) @@ -143,5 +147,23 @@ namespace Firebase.Notifications @} public extern(!iOS && !Android) static void ClearAllNotifications() { } + + [Foreign(Language.ObjC)] + public extern(iOS) static String GetFCMToken() + @{ + NSString *fcmToken = [[FIRInstanceID instanceID] token]; + return fcmToken; + @} + + [Foreign(Language.Java)] + public extern(Android) static String GetFCMToken() + @{ + String refreshedToken = FirebaseInstanceId.getInstance().getToken(); + return refreshedToken; + @} + + public extern(!iOS && !Android) static String GetFCMToken() { return ""; } + + } } diff --git a/src/Firebase.Notifications.Android/Firebase.Notifications.Android.unoproj b/src/Firebase.Notifications.Android/Firebase.Notifications.Android.unoproj index 708c3d9..e2a1ed0 100644 --- a/src/Firebase.Notifications.Android/Firebase.Notifications.Android.unoproj +++ b/src/Firebase.Notifications.Android/Firebase.Notifications.Android.unoproj @@ -22,11 +22,13 @@ "Includes": [ "Common.uno:Source", "JS.uno:Source", - "Android/Impl.uno:Source", + "iOSImpl.uno:Source", "Android/Impl.cpp.uxl:Extensions", "Android/PushNotificationReceiver.java:Java:android", "Android/PushNotificationIDService.java:Java:android", + "iOSFirebaseNotificationCallbacks.h:ObjCHeader:iOS", + "iOSFirebaseNotificationCallbacks.mm:ObjCSource:iOS", "Android/Assets/DefaultIcon.png:File" ] } diff --git a/src/Firebase.Notifications.Android/JS.uno b/src/Firebase.Notifications.Android/JS.uno index 8db1ae7..6a7e335 100644 --- a/src/Firebase.Notifications.Android/JS.uno +++ b/src/Firebase.Notifications.Android/JS.uno @@ -26,6 +26,8 @@ namespace Firebase.Notifications public sealed class NotificationModule : NativeEventEmitterModule { static readonly NotificationModule _instance; + readonly iOSImpl _iOSImpl; + static NativeEvent _onRegistrationSucceedediOS; public NotificationModule() : base(true, @@ -35,6 +37,8 @@ namespace Firebase.Notifications if (_instance != null) return; Resource.SetGlobalKey(_instance = this, "Firebase/Notifications"); + _iOSImpl = new iOSImpl(); + // Old-style events for backwards compatibility var onReceivedMessage = new NativeEvent("onReceivedMessage"); var onRegistrationFailed = new NativeEvent("onRegistrationFailed"); @@ -55,6 +59,9 @@ namespace Firebase.Notifications AddMember(onRegistrationFailed); AddMember(new NativeFunction("clearBadgeNumber", ClearBadgeNumber)); AddMember(new NativeFunction("clearAllNotifications", ClearAllNotifications)); + AddMember(new NativeFunction("getFCMToken", GetFCMToken)); + _onRegistrationSucceedediOS = new NativeEvent("onRegistrationSucceedediOS"); + AddMember(_onRegistrationSucceedediOS); Firebase.Notifications.NotificationService.ReceivedNotification += OnReceivedNotification; Firebase.Notifications.NotificationService.RegistrationSucceeded += OnRegistrationSucceeded; @@ -83,6 +90,11 @@ namespace Firebase.Notifications Emit("registrationSucceeded", message); } + static void OnRegistrationSucceedediOS(string message) { + //_onRegistrationSucceedediOS.RaiseAsync(message); + // App is getting crash sometimes at this function and now we are getting FCM token via GetFCMToken(), so we can put it in comment + } + /** @scriptevent error @param message A backend specific reason for the failure. @@ -117,5 +129,14 @@ namespace Firebase.Notifications Firebase.Notifications.NotificationService.ClearAllNotifications(); return null; } + + public object GetFCMToken(Context context, object[] args) + { + var token = Firebase.Notifications.NotificationService.GetFCMToken(); + if (token != null) { + Emit("registrationSucceeded", token); + } + return null; + } } } diff --git a/src/Firebase.Notifications.Android/iOSFirebaseNotificationCallbacks.h b/src/Firebase.Notifications.Android/iOSFirebaseNotificationCallbacks.h new file mode 100644 index 0000000..659ea5f --- /dev/null +++ b/src/Firebase.Notifications.Android/iOSFirebaseNotificationCallbacks.h @@ -0,0 +1,5 @@ +#import +#include + +@interface FireNotificationCallbacks : NSObject +@end diff --git a/src/Firebase.Notifications.Android/iOSFirebaseNotificationCallbacks.mm b/src/Firebase.Notifications.Android/iOSFirebaseNotificationCallbacks.mm new file mode 100644 index 0000000..42e0f88 --- /dev/null +++ b/src/Firebase.Notifications.Android/iOSFirebaseNotificationCallbacks.mm @@ -0,0 +1,13 @@ +#import +#include <@{Firebase.Notifications.NotificationModule:Include}> +#include <@{ObjC.Object:Include}> +#include + +@implementation FireNotificationCallbacks : NSObject + + +- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { + @{Firebase.Notifications.NotificationModule.OnRegistrationSucceedediOS(string):Call(fcmToken)}; +} + +@end diff --git a/src/Firebase.Notifications.Android/iOSImpl.uno b/src/Firebase.Notifications.Android/iOSImpl.uno new file mode 100644 index 0000000..7aefb70 --- /dev/null +++ b/src/Firebase.Notifications.Android/iOSImpl.uno @@ -0,0 +1,35 @@ +using Uno; +using Uno.UX; +using Uno.Collections; +using Uno.Compiler.ExportTargetInterop; +using Fuse; +using Fuse.Triggers; +using Uno.Threading; + +namespace Firebase.Notifications +{ + [Require("Cocoapods.Podfile.Target", "pod 'Firebase/Messaging'")] + [extern(iOS) Require("Source.Include", "iOSFirebaseNotificationCallbacks.h")] + [extern(iOS) Require("Source.Include", "Firebase/Firebase.h")] + + public class iOSImpl + { + extern(ios) static internal ObjC.Object _iosDelegate; + + public iOSImpl() { + Start(); + } + + extern(!iOS) + public void Start() { } + + [Foreign(Language.ObjC)] + extern(iOS) + public void Start() + @{ + FireNotificationCallbacks* fireCB = [[FireNotificationCallbacks alloc] init]; + @{_iosDelegate:Set(fireCB)}; + [FIRMessaging messaging].delegate = (id)fireCB; + @} + } +} diff --git a/src/Firebase.Storage/Firebase.Storage.unoproj b/src/Firebase.Storage/Firebase.Storage.unoproj new file mode 100755 index 0000000..a70ae9e --- /dev/null +++ b/src/Firebase.Storage/Firebase.Storage.unoproj @@ -0,0 +1,12 @@ +{ + "Projects": [ + "../Firebase/Firebase.unoproj" + ], + "Packages": [ + "Fuse", + "FuseJS" + ], + "Includes": [ + "*" + ] +} diff --git a/src/Firebase.Storage/JS.uno b/src/Firebase.Storage/JS.uno new file mode 100755 index 0000000..303d45c --- /dev/null +++ b/src/Firebase.Storage/JS.uno @@ -0,0 +1,33 @@ +using Uno; +using Uno.UX; +using Uno.Threading; +using Uno.Text; +using Uno.Platform; +using Uno.Compiler.ExportTargetInterop; +using Uno.Collections; +using Fuse; +using Fuse.Scripting; +using Fuse.Reactive; + +namespace Firebase.Storage.JS +{ + [UXGlobalModule] + public sealed class StorageModule : NativeModule + { + static readonly StorageModule _instance; + public StorageModule() + { + if(_instance != null) return; + Uno.UX.Resource.SetGlobalKey(_instance = this, "Firebase/Storage"); + Firebase.Storage.StorageService.Init(); + AddMember(new NativePromise("upload", Upload, null)); + } + + static Future Upload(object[] args) + { + var storagepath = args[0].ToString(); + var filepath = args[1].ToString(); + return new Upload(storagepath, filepath); + } + } +} diff --git a/src/Firebase.Storage/Storage.uno b/src/Firebase.Storage/Storage.uno new file mode 100755 index 0000000..2220482 --- /dev/null +++ b/src/Firebase.Storage/Storage.uno @@ -0,0 +1,128 @@ +using Uno; +using Uno.UX; +using Uno.Collections; +using Uno.Compiler.ExportTargetInterop; +using Fuse; +using Fuse.Triggers; +using Fuse.Controls; +using Fuse.Controls.Native; +using Fuse.Controls.Native.Android; +using Uno.Threading; + +namespace Firebase.Storage +{ + [ForeignInclude(Language.Java, + "com.google.firebase.storage.FirebaseStorage", + "com.google.firebase.storage.OnProgressListener", + "com.google.firebase.storage.StorageReference", + "com.google.firebase.storage.UploadTask")] + [Require("Cocoapods.Podfile.Target", "pod 'Firebase/Storage'")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-storage:11.8.0")] + [extern(iOS) Require("Source.Import","FirebaseStorage/FirebaseStorage.h")] + extern(mobile) + static class StorageService + { + static bool _initialized; + extern(android) static Java.Object _handle; + extern(ios) public static ObjC.Object _handle; + + public static void Init() + { + if (!_initialized) + { + if defined(android) AndroidInit(); + if defined(ios) iOSInit(); + _initialized = true; + } + } + + [Foreign(Language.ObjC)] + extern(iOS) + public static void iOSInit() + @{ + [FIRStorage storage]; + @{_handle:Set([[FIRStorage storage] reference])}; + @} + + + [Foreign(Language.Java)] + extern(android) + public static void AndroidInit() + @{ + @{_handle:Set(FirebaseStorage.getInstance().getReference())}; + @} + } + + extern(!mobile) + static class StorageService + { + public static void Init() {} + } + + extern(!mobile) + internal class Upload : Promise + { + public Upload(string storagepath, string filepath) + { + Reject(new Exception("Not implemented on desktop")); + } + } + + [Require("Entity", "StorageService")] + [Require("Source.Include","@{StorageService:Include}")] + [extern(iOS) Require("Source.Import","FirebaseStorage/FirebaseStorage.h")] + extern(iOS) + internal class Upload : Promise + { + [Foreign(Language.ObjC)] + public Upload(string storagepath, string filepath) + @{ + FIRStorageReference *ref = @{StorageService._handle:Get()}; + NSURL *localFile = [NSURL fileURLWithPath:filepath]; + FIRStorageUploadTask *uploadTask = [[ref child:storagepath] putFile:localFile metadata:nil completion:^(FIRStorageMetadata *metadata, NSError *error) { + if (error != nil) { + NSString *erstr = [NSString stringWithFormat:@"Firebase Storage Upload Error: %@", error.localizedDescription]; + @{Upload:Of(_this).Reject(string):Call(erstr)}; + } else { + NSURL *downloadURL = metadata.downloadURL; + @{Upload:Of(_this).Resolve(string):Call([downloadURL absoluteString])}; + } + }]; + @} + void Reject(string reason) { Reject(new Exception(reason)); } + } + + [ForeignInclude(Language.Java, + "com.google.android.gms.tasks.OnFailureListener", + "com.google.android.gms.tasks.OnSuccessListener", + "com.google.firebase.storage.FirebaseStorage", + "com.google.firebase.storage.OnProgressListener", + "com.google.firebase.storage.StorageReference", + "com.google.firebase.storage.UploadTask", + "java.io.File", + "android.net.Uri")] + extern(Android) + internal class Upload : Promise + { + [Foreign(Language.Java)] + public Upload(string storagepath, string filepath) + @{ + StorageReference ref = (StorageReference)@{StorageService._handle:Get()}; + Uri file = Uri.fromFile(new File(filepath)); + StorageReference childRef = ref.child(storagepath); + + childRef.putFile(file).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception exception) { + @{Upload:Of(_this).Reject(string):Call(exception.toString())}; + } + }).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { + @{Upload:Of(_this).Resolve(string):Call(taskSnapshot.getDownloadUrl().toString())}; + } + }); + @} + void Reject(string reason) { Reject(new Exception(reason)); } + } +} diff --git a/src/Firebase/Core.uno b/src/Firebase/Core.uno index f879631..d9786e1 100644 --- a/src/Firebase/Core.uno +++ b/src/Firebase/Core.uno @@ -12,7 +12,7 @@ namespace Firebase { [ForeignInclude(Language.Java, "java.util.ArrayList", "java.util.List", "android.graphics.Color")] [Require("Gradle.Dependency.ClassPath", "com.google.gms:google-services:3.0.0")] - [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-core:9.2.0")] + [Require("Gradle.Dependency.Compile", "com.google.firebase:firebase-core:11.8.0")] [Require("Gradle.BuildFile.End", "apply plugin: 'com.google.gms.google-services'")] [Require("Cocoapods.Podfile.Target", "pod 'Firebase/Core'")]