From ed409a2c7a04a8cfc832bf05ffb3b4912ce53dcd Mon Sep 17 00:00:00 2001 From: kphayen Date: Mon, 7 Nov 2016 17:52:49 -0700 Subject: [PATCH 01/23] Implement reauthenticateWithCredentialForProvider for Android --- .../io/fullstack/firestack/FirestackAuth.java | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 7c0c2bb..d171c90 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -187,10 +187,41 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void reauthenticateWithCredentialForProvider(final String provider, final String authToken, final String authSecret, final Callback callback) { - // TODO: - FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); - // AuthCredential credential; - // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); + AuthCredential credential; + + if (provider.equals("facebook")) { + credential = FacebookAuthProvider.getCredential(authToken); + } else if (provider.equals("google")) { + credential = GoogleAuthProvider.getCredential(authToken, null); + } else { + // TODO: + FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); + // AuthCredential credential; + // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); + return; + } + + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if (user != null) { + user.reauthenticate(credential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "User re-authenticated with " + provider); + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + userCallback(u, callback); + } else { + userErrorCallback(task, callback); + } + } + }); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", NO_CURRENT_USER); + err.putString("errorMessage", "No current user"); + callback.invoke(err); + } } @ReactMethod From 972b16d27d83791db5ff66f043bab883d9270762 Mon Sep 17 00:00:00 2001 From: Chaitanya Bhagvan Date: Tue, 8 Nov 2016 19:24:04 +0530 Subject: [PATCH 02/23] Catch exception thrown by setPersistenceEnabled which was causing the rn app to fail --- android/.idea/gradle.xml | 1 + .../main/java/io/fullstack/firestack/FirestackDatabase.java | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml index f4f39e8..76a4349 100644 --- a/android/.idea/gradle.xml +++ b/android/.idea/gradle.xml @@ -5,6 +5,7 @@ diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 61cda5d..37aeca3 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java @@ -304,8 +304,12 @@ public String getName() { public void enablePersistence( final Boolean enable, final Callback callback) { - FirebaseDatabase.getInstance() + try { + FirebaseDatabase.getInstance() .setPersistenceEnabled(enable); + } catch (Throwable t) { + Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t); + } WritableMap res = Arguments.createMap(); res.putString("status", "success"); From a4a115f16d55fd9052b5f68d72936d067c67f193 Mon Sep 17 00:00:00 2001 From: Chaitanya Bhagvan Date: Tue, 8 Nov 2016 19:25:36 +0530 Subject: [PATCH 03/23] Parity in response format between ios and android --- .../java/io/fullstack/firestack/FirestackAuth.java | 4 ++-- ios/Firestack/FirestackAuth.m | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 7c0c2bb..7d3f889 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -432,7 +432,6 @@ public void onComplete(@NonNull Task task) { WritableMap userMap = getUserMap(); if (FirestackAuthModule.this.user != null) { final String token = task.getResult().getToken(); - userMap.putString("token", token); userMap.putBoolean("anonymous", false); } @@ -509,9 +508,10 @@ private WritableMap getUserMap() { userMap.putString("email", email); userMap.putString("uid", uid); userMap.putString("providerId", provider); + userMap.putBoolean("emailVerified", user.isEmailVerified()); if (name != null) { - userMap.putString("name", name); + userMap.putString("displayName", name); } if (photoUrl != null) { diff --git a/ios/Firestack/FirestackAuth.m b/ios/Firestack/FirestackAuth.m index 0eba61c..287c52b 100644 --- a/ios/Firestack/FirestackAuth.m +++ b/ios/Firestack/FirestackAuth.m @@ -25,7 +25,7 @@ @implementation FirestackAuth if (!user) { NSDictionary *evt = @{ @"eventName": AUTH_ANONYMOUS_ERROR_EVENT, - @"msg": [error localizedDescription] + @"errorMessage": [error localizedDescription] }; @@ -41,7 +41,7 @@ @implementation FirestackAuth } @catch(NSException *ex) { NSDictionary *eventError = @{ @"eventName": AUTH_ANONYMOUS_ERROR_EVENT, - @"msg": ex.reason + @"errorMessage": ex.reason }; [self sendJSEvent:AUTH_ERROR_EVENT @@ -144,14 +144,15 @@ @implementation FirestackAuth sendJSEvent:AUTH_CHANGED_EVENT props: @{ @"eventName": @"userTokenError", - @"msg": [error localizedFailureReason] + @"authenticated": @((BOOL)true), + @"errorMessage": [error localizedFailureReason] }]; } else { [self sendJSEvent:AUTH_CHANGED_EVENT props: @{ @"eventName": @"user", - @"authenticated": @(true), + @"authenticated": @((BOOL)true), @"user": userProps }]; } @@ -164,7 +165,7 @@ @implementation FirestackAuth [self sendJSEvent:AUTH_CHANGED_EVENT props:@{ @"eventName": @"no_user", - @"authenticated": @(false), + @"authenticated": @((BOOL)false), @"error": err }]; } From 483e15fd4b7a8256bec99771220c4cc9721e198a Mon Sep 17 00:00:00 2001 From: Chaitanya Bhagvan Date: Tue, 8 Nov 2016 20:19:53 +0530 Subject: [PATCH 04/23] Fixed a typo that was causing an error --- lib/modules/database.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/database.js b/lib/modules/database.js index f7f91fe..0cf1382 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -232,7 +232,7 @@ class DatabaseRef extends ReferenceBase { const path = this.dbPath(); return this.db.off(path, evt, origCB) .then(({callback, subscriptions}) => { - if (dbSubscriptions[path] && dbSubscriptions[path][evt].length > 0) { + if (subscriptions[path] && subscriptions[path][evt].length > 0) { return subscriptions; } @@ -514,4 +514,4 @@ export class Database extends Base { } } -export default Database \ No newline at end of file +export default Database From a85f5facacc4ecb6af0924e88c222f6d4d4c3747 Mon Sep 17 00:00:00 2001 From: Chaitanya Bhagvan Date: Wed, 9 Nov 2016 14:50:11 +0530 Subject: [PATCH 05/23] Parity in response format between ios and android --- ios/Firestack/FirestackAuth.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/Firestack/FirestackAuth.m b/ios/Firestack/FirestackAuth.m index 287c52b..bcd0919 100644 --- a/ios/Firestack/FirestackAuth.m +++ b/ios/Firestack/FirestackAuth.m @@ -186,7 +186,8 @@ @implementation FirestackAuth FIRUser *user = [FIRAuth auth].currentUser; if (user != nil) { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; + NSMutableDictionary *userProps = [self userPropsFromFIRUser:user]; + [userProps setValue: @((BOOL)true) forKey: @"authenticated"]; callback(@[[NSNull null], userProps]); } else { // No user is signed in. From e34988b436d027ca9ec1f036d07d79c10748e1ec Mon Sep 17 00:00:00 2001 From: Chaitanya Bhagvan Date: Wed, 9 Nov 2016 19:22:55 +0530 Subject: [PATCH 06/23] Reverted changes since it was an error from my end --- lib/modules/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/database.js b/lib/modules/database.js index 0cf1382..6affe14 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -232,7 +232,7 @@ class DatabaseRef extends ReferenceBase { const path = this.dbPath(); return this.db.off(path, evt, origCB) .then(({callback, subscriptions}) => { - if (subscriptions[path] && subscriptions[path][evt].length > 0) { + if (dbSubscriptions[path] && dbSubscriptions[path][evt].length > 0) { return subscriptions; } From 68396e07542cedd66fd61fad48554f5cdebd8430 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Wed, 9 Nov 2016 14:53:22 +0000 Subject: [PATCH 07/23] fixing filter value creation --- lib/modules/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/database.js b/lib/modules/database.js index f7f91fe..b87d429 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -113,7 +113,7 @@ class DatabaseQuery { .forEach(key => { const filter = this.filters[key]; if (filter) { - const filterArgs = [key, filter].join(argsSeparator) + const filterArgs = ([key].concat(filter)).join(argsSeparator) modifiers.push(filterArgs); } }) From c3129fbb16663c4813b5c4b2fa58265e33c41a31 Mon Sep 17 00:00:00 2001 From: ghuh Date: Wed, 9 Nov 2016 19:04:54 -0700 Subject: [PATCH 08/23] Fix Android listenForAuth method to correctly check for user --- android/src/main/java/io/fullstack/firestack/FirestackAuth.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index e9ec2bf..9f1372a 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -68,7 +68,7 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { WritableMap msgMap = Arguments.createMap(); msgMap.putString("eventName", "listenForAuth"); - if (FirestackAuthModule.this.user != null) { + if (firebaseAuth.getCurrentUser() != null) { WritableMap userMap = getUserMap(); msgMap.putBoolean("authenticated", true); From 28644a6560e12576b7243732f46ae1a7990decc1 Mon Sep 17 00:00:00 2001 From: Ari Lerner Date: Wed, 9 Nov 2016 20:26:11 -0800 Subject: [PATCH 09/23] WIP: FirebaseAuth --- .../io/fullstack/firestack/FirestackAuth.java | 193 +++++++++++++----- lib/utils/window-or-global.js | 2 +- 2 files changed, 138 insertions(+), 57 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index e9ec2bf..050768e 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -1,3 +1,4 @@ + package io.fullstack.firestack; import android.content.Context; @@ -99,7 +100,7 @@ public void unlistenForAuth(final Callback callback) { } @ReactMethod - public void createUserWithEmail(final String email, final String password, final Callback onComplete) { + public void createUserWithEmail(final String email, final String password, final Callback callback) { mAuth = FirebaseAuth.getInstance(); mAuth.createUserWithEmailAndPassword(email, password) @@ -107,13 +108,18 @@ public void createUserWithEmail(final String email, final String password, final @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, onComplete); - }else{ - userErrorCallback(task, onComplete); + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + // userErrorCallback(task, callback); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -125,13 +131,19 @@ public void signInWithEmail(final String email, final String password, final Cal @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); } else { - userErrorCallback(task, callback); + // userErrorCallback(task, callback); } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + Log.e(TAG, "An exception occurred: " + ex.getMessage()); + userExceptionCallback(ex, callback); } - }); + }); } @ReactMethod @@ -156,14 +168,18 @@ public void onComplete(@NonNull Task task) { Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful()); if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - anonymousUserCallback(FirestackAuthModule.this.user, callback); - }else{ - userErrorCallback(task, callback); + FirestackAuthModule.this.user = task.getResult().getUser(); + anonymousUserCallback(FirestackAuthModule.this.user, callback); + } else { + // userErrorCallback(task, callback); } - } - }); - + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -175,14 +191,19 @@ public void signInWithCustomToken(final String customToken, final Callback callb @Override public void onComplete(@NonNull Task task) { Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful()); - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); userCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } + } else { + // userErrorCallback(task, callback); + } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -212,7 +233,7 @@ public void onComplete(@NonNull Task task) { FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); userCallback(u, callback); } else { - userErrorCallback(task, callback); + // userErrorCallback(task, callback); } } }); @@ -238,10 +259,15 @@ public void onComplete(@NonNull Task task) { FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); userCallback(u, callback); } else { - userErrorCallback(task, callback); + // userErrorCallback(task, callback); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } else { WritableMap err = Arguments.createMap(); err.putInt("errorCode", NO_CURRENT_USER); @@ -265,10 +291,15 @@ public void onComplete(@NonNull Task task) { FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); userCallback(u, callback); } else { - userErrorCallback(task, callback); + // userErrorCallback(task, callback); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } else { WritableMap err = Arguments.createMap(); err.putInt("errorCode", NO_CURRENT_USER); @@ -289,11 +320,16 @@ public void onComplete(@NonNull Task task) { WritableMap resp = Arguments.createMap(); resp.putString("status", "complete"); callback.invoke(null, resp); - }else{ + } else { callback.invoke(task.getException().toString()); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -312,10 +348,15 @@ public void onComplete(@NonNull Task task) { resp.putString("msg", "User account deleted"); callback.invoke(null, resp); } else { - userErrorCallback(task, callback); + // userErrorCallback(task, callback); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } else { WritableMap err = Arguments.createMap(); err.putInt("errorCode", NO_CURRENT_USER); @@ -345,7 +386,12 @@ public void onComplete(@NonNull Task task) { callback.invoke(err); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -378,10 +424,15 @@ public void onComplete(@NonNull Task task) { FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); userCallback(u, callback); } else { - userErrorCallback(task, callback); + // userErrorCallback(task, callback); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -417,14 +468,19 @@ public void googleLogin(String IdToken, final Callback callback) { .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - }else{ - userErrorCallback(task, callback); - } + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + }else{ + // userErrorCallback(task, callback); + } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -436,18 +492,23 @@ public void facebookLogin(String Token, final Callback callback) { .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - }else{ - userErrorCallback(task, callback); - } + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + }else{ + // userErrorCallback(task, callback); + } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } // Internal helpers - public void userCallback(FirebaseUser passedUser, final Callback onComplete) { + public void userCallback(FirebaseUser passedUser, final Callback callback) { if (passedUser == null) { mAuth = FirebaseAuth.getInstance(); @@ -469,13 +530,18 @@ public void onComplete(@NonNull Task task) { msgMap.putMap("user", userMap); - onComplete.invoke(null, msgMap); + callback.invoke(null, msgMap); } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } // TODO: Reduce to one method - public void anonymousUserCallback(FirebaseUser passedUser, final Callback onComplete) { + public void anonymousUserCallback(FirebaseUser passedUser, final Callback callback) { if (passedUser == null) { mAuth = FirebaseAuth.getInstance(); @@ -484,7 +550,8 @@ public void anonymousUserCallback(FirebaseUser passedUser, final Callback onComp this.user = passedUser; } - this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { + this.user.getToken(true) + .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { WritableMap msgMap = Arguments.createMap(); @@ -499,9 +566,14 @@ public void onComplete(@NonNull Task task) { msgMap.putMap("user", userMap); - onComplete.invoke(null, msgMap); + callback.invoke(null, msgMap); } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @@ -524,6 +596,15 @@ public void userErrorCallback(Task task, final Callback onFail) { onFail.invoke(error); } + public void userExceptionCallback(Exception ex, final Callback onFail) { + WritableMap error = Arguments.createMap(); + error.putInt("errorCode", ex.hashCode()); + error.putString("errorMessage", ex.getMessage()); + error.putString("allErrorMessage", ex.toString()); + + onFail.invoke(error); + } + private WritableMap getUserMap() { WritableMap userMap = Arguments.createMap(); diff --git a/lib/utils/window-or-global.js b/lib/utils/window-or-global.js index 3228c06..7b64020 100644 --- a/lib/utils/window-or-global.js +++ b/lib/utils/window-or-global.js @@ -2,4 +2,4 @@ // https://github.com/purposeindustries/window-or-global module.exports = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || - this \ No newline at end of file + {} \ No newline at end of file From 1111617f2b07e38b0ad1849c4eb919723da2ffc4 Mon Sep 17 00:00:00 2001 From: Ari Lerner Date: Thu, 10 Nov 2016 02:55:43 -0800 Subject: [PATCH 10/23] Added initial handling of android error messages --- .../io/fullstack/firestack/FirestackAuth.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 050768e..d9d4101 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -33,6 +33,7 @@ import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.GetTokenResult; import com.google.firebase.auth.GoogleAuthProvider; +import com.google.firebase.auth.FirebaseAuthException; class FirestackAuthModule extends ReactContextBaseJavaModule { private final int NO_CURRENT_USER = 100; @@ -588,19 +589,21 @@ public void noUserCallback(final Callback callback) { } public void userErrorCallback(Task task, final Callback onFail) { - WritableMap error = Arguments.createMap(); - error.putInt("errorCode", task.getException().hashCode()); - error.putString("errorMessage", task.getException().getMessage()); - error.putString("allErrorMessage", task.getException().toString()); - - onFail.invoke(error); + userExceptionCallback(task.getException(), onFail); } - public void userExceptionCallback(Exception ex, final Callback onFail) { + public void userExceptionCallback(Exception exp, final Callback onFail) { WritableMap error = Arguments.createMap(); - error.putInt("errorCode", ex.hashCode()); - error.putString("errorMessage", ex.getMessage()); - error.putString("allErrorMessage", ex.toString()); + error.putString("errorMessage", exp.getMessage()); + error.putString("allErrorMessage", exp.toString()); + + try { + throw exp; + } catch (FirebaseAuthException ex) { + error.putString("errorCode", ex.getErrorCode()); + } catch (Exception ex) { + Log.e(TAG, ex.getMessage()); + } onFail.invoke(error); } @@ -636,3 +639,6 @@ private WritableMap getUserMap() { return userMap; } } +n userMap; + } +} From 7d09ab4a7aba582290e1bebf6692dadaba3e38e7 Mon Sep 17 00:00:00 2001 From: Chaitanya Bhagvan Date: Fri, 11 Nov 2016 20:30:56 +0530 Subject: [PATCH 11/23] Parity of response format between ios and android. Added authenticated flag to response from getCurrentUser --- android/src/main/java/io/fullstack/firestack/FirestackAuth.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index a155612..36dd584 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -530,7 +530,7 @@ public void onComplete(@NonNull Task task) { } msgMap.putMap("user", userMap); - + msgMap.putBoolean("authenticated", true); callback.invoke(null, msgMap); } }).addOnFailureListener(new OnFailureListener() { From e5255698ae07a84e7e946c1666e1f430616581a1 Mon Sep 17 00:00:00 2001 From: Ari Lerner Date: Mon, 14 Nov 2016 08:22:34 -0800 Subject: [PATCH 12/23] Remove RCTConvert to work with react-native-fcm --- ios/Firestack/FirestackCloudMessaging.m | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/ios/Firestack/FirestackCloudMessaging.m b/ios/Firestack/FirestackCloudMessaging.m index 9cfa6b3..0a71a64 100644 --- a/ios/Firestack/FirestackCloudMessaging.m +++ b/ios/Firestack/FirestackCloudMessaging.m @@ -13,28 +13,7 @@ #endif #import "FirestackCloudMessaging.h" #import "FirestackEvents.h" -#import "RCTConvert.h" - -// https://github.com/facebook/react-native/blob/master/Libraries/PushNotificationIOS/RCTPushNotificationManager.m -@implementation RCTConvert (UILocalNotification) - -+ (UILocalNotification *)UILocalNotification:(id)json -{ - NSDictionary *details = [self NSDictionary:json]; - UILocalNotification *notification = [UILocalNotification new]; - notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; - notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; - notification.alertAction = [RCTConvert NSString:details[@"alertAction"]]; - notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; - notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; - notification.category = [RCTConvert NSString:details[@"category"]]; - if (details[@"applicationIconBadgeNumber"]) { - notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"applicationIconBadgeNumber"]]; - } - return notification; -} - -@end +// #import "RCTConvert.h" @implementation FirestackCloudMessaging From 8306c45f3be5be063b3eebc362ea7d3ca1ab2505 Mon Sep 17 00:00:00 2001 From: Samer Date: Wed, 16 Nov 2016 14:38:18 +0100 Subject: [PATCH 13/23] Filter out undefined filters --- lib/modules/database.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/modules/database.js b/lib/modules/database.js index 5c4b27e..6ebdecc 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -111,9 +111,10 @@ class DatabaseQuery { } Object.keys(this.filters) .forEach(key => { - const filter = this.filters[key]; + let filter = this.filters[key]; if (filter) { - const filterArgs = ([key].concat(filter)).join(argsSeparator) + const cleanFilters = filter.filter((f) => typeof f !== "undefined"); + const filterArgs = ([key].concat(cleanFilters)).join(argsSeparator); modifiers.push(filterArgs); } }) From 487ed30886c1cba943d34cde67f565a0a92c4881 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Wed, 16 Nov 2016 18:23:19 +0000 Subject: [PATCH 14/23] removing trailing comma --- lib/modules/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/database.js b/lib/modules/database.js index 5c4b27e..9956f39 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -96,7 +96,7 @@ class DatabaseQuery { } setFilter(name, ...args) { - this.filters[name] = args; + this.filters[name] = args.filter(n => n != undefined); return this.ref; } From 6ca78a19ea7d611605ddf82f151c13c6534ac098 Mon Sep 17 00:00:00 2001 From: Chaitanya Bhagvan Date: Thu, 17 Nov 2016 11:46:52 +0530 Subject: [PATCH 15/23] forEach and map method on the DataSnapshot does not retain key --- lib/modules/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/database.js b/lib/modules/database.js index 5c4b27e..ff59c74 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -35,7 +35,7 @@ class DataSnapshot { forEach(fn) { (this.childKeys || []) - .forEach(key => fn(this.value[key])) + .forEach(key => fn({key: key, value: this.value[key]})) } map(fn) { From 80133de8316729a851610ee6d326c73a642dde32 Mon Sep 17 00:00:00 2001 From: Chaitanya Bhagvan Date: Thu, 17 Nov 2016 12:30:32 +0530 Subject: [PATCH 16/23] Fixed error calling length of undefined --- lib/modules/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/database.js b/lib/modules/database.js index ff59c74..1112919 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -232,7 +232,7 @@ class DatabaseRef extends ReferenceBase { const path = this.dbPath(); return this.db.off(path, evt, origCB) .then(({callback, subscriptions}) => { - if (dbSubscriptions[path] && dbSubscriptions[path][evt].length > 0) { + if (dbSubscriptions[path] && dbSubscriptions[path][evt] && dbSubscriptions[path][evt].length > 0) { return subscriptions; } From d005ab2f966cca23e955ca79f109efbefced8772 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 17 Nov 2016 20:05:09 +0100 Subject: [PATCH 17/23] Use storage url from .plist file if not setStorageUrl() hasn't been called. - Fix weird error that NSError is not KVC compliant when storage is not configured JS - Fixed code seems to exist on several more locations but has not been fixed, since there are no tests to ensure I don't break anything. --- ios/Firestack/FirestackStorage.m | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ios/Firestack/FirestackStorage.m b/ios/Firestack/FirestackStorage.m index d88a19a..ff96646 100644 --- a/ios/Firestack/FirestackStorage.m +++ b/ios/Firestack/FirestackStorage.m @@ -52,14 +52,13 @@ - (dispatch_queue_t)methodQueue metadata:(NSDictionary *)metadata callback:(RCTResponseSenderBlock) callback) { + FIRStorageReference *storageRef; if (urlStr == nil) { - NSError *err = [[NSError alloc] init]; - [err setValue:@"Storage configuration error" forKey:@"name"]; - [err setValue:@"Call setStorageUrl() first" forKey:@"description"]; - return callback(@[err]); + storageRef = [[FIRStorage storage] reference]; + } else { + storageRef = [[FIRStorage storage] referenceForURL:urlStr]; } - FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:urlStr]; FIRStorageReference *uploadRef = [storageRef child:name]; FIRStorageMetadata *firmetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata]; From dfc0bd1eb86706a421341fdf9b6a6f27e3931d97 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 17 Nov 2016 20:51:23 +0100 Subject: [PATCH 18/23] Fix donwloadUrl to use storageUrl from .plist. - Use storageUrl from default config if not set otherwise in JS - Similar code exists on several other places that are not fixed since no tests prevent breaking stuff. --- ios/Firestack/FirestackStorage.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ios/Firestack/FirestackStorage.m b/ios/Firestack/FirestackStorage.m index ff96646..b87d394 100644 --- a/ios/Firestack/FirestackStorage.m +++ b/ios/Firestack/FirestackStorage.m @@ -25,7 +25,12 @@ - (dispatch_queue_t)methodQueue path:(NSString *) path callback:(RCTResponseSenderBlock) callback) { - FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:storageUrl]; + FIRStorageReference *storageRef; + if (storageUrl == nil ) { + storageRef = [[FIRStorage storage] reference]; + } else { + storageRef = [[FIRStorage storage] referenceForURL:storageUrl]; + } FIRStorageReference *fileRef = [storageRef child:path]; [fileRef downloadURLWithCompletion:^(NSURL * _Nullable URL, NSError * _Nullable error) { if (error != nil) { From f4bcfea81abb1c0d8fb37f02e912700ede340a4e Mon Sep 17 00:00:00 2001 From: Ari Lerner Date: Thu, 17 Nov 2016 20:24:38 -0500 Subject: [PATCH 19/23] WIP --- ios/Firestack/FirestackCloudMessaging.m | 55 ++++++++++++++----------- ios/Firestack/FirestackStorage.m | 1 + lib/modules/cloudmessaging.js | 24 ++++++++++- 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/ios/Firestack/FirestackCloudMessaging.m b/ios/Firestack/FirestackCloudMessaging.m index 0a71a64..ea8a346 100644 --- a/ios/Firestack/FirestackCloudMessaging.m +++ b/ios/Firestack/FirestackCloudMessaging.m @@ -68,34 +68,39 @@ + (void) setup:(UIApplication *) application selector:@selector(handleTokenRefresh) name:kFIRInstanceIDTokenRefreshNotification object: nil]; +} +#pragma mark Request permissions +- (void) requestPermissions(NSDictionary *)requestedPermissions + callback:(RCTResponseSenderBlock) callback +{ if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"9.0")) { - UIUserNotificationType allNotificationTypes = - (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); - UIUserNotificationSettings *settings = - [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil]; - [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; -} else { - // iOS 10 or later - #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - UNAuthorizationOptions authOptions = - UNAuthorizationOptionAlert - | UNAuthorizationOptionSound - | UNAuthorizationOptionBadge; - [[UNUserNotificationCenter currentNotificationCenter] - requestAuthorizationWithOptions:authOptions - completionHandler:^(BOOL granted, NSError * _Nullable error) { - } - ]; - - // For iOS 10 display notification (sent via APNS) - [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; - // For iOS 10 data message (sent via FCM) - [[FIRMessaging messaging] setRemoteMessageDelegate:self]; - #endif -} + UIUserNotificationType allNotificationTypes = + (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); + UIUserNotificationSettings *settings = + [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil]; + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + } else { + // iOS 10 or later + #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + UNAuthorizationOptions authOptions = + UNAuthorizationOptionAlert + | UNAuthorizationOptionSound + | UNAuthorizationOptionBadge; + [[UNUserNotificationCenter currentNotificationCenter] + requestAuthorizationWithOptions:authOptions + completionHandler:^(BOOL granted, NSError * _Nullable error) { + } + ]; + + // For iOS 10 display notification (sent via APNS) + [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; + // For iOS 10 data message (sent via FCM) + [[FIRMessaging messaging] setRemoteMessageDelegate:self]; + #endif + } -[[UIApplication sharedApplication] registerForRemoteNotifications]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; } #pragma mark callbacks diff --git a/ios/Firestack/FirestackStorage.m b/ios/Firestack/FirestackStorage.m index d88a19a..3e4511a 100644 --- a/ios/Firestack/FirestackStorage.m +++ b/ios/Firestack/FirestackStorage.m @@ -157,6 +157,7 @@ - (void) addUploadObservers:(FIRStorageUploadTask *) uploadTask case FIRStorageErrorCodeUnknown: // Unknown error occurred, inspect the server response [errProps setValue:@"Unknown error" forKey:@"description"]; + NSLog(@"Unknown error: %@", snapshot.error); break; } diff --git a/lib/modules/cloudmessaging.js b/lib/modules/cloudmessaging.js index a03a2ed..73b390b 100644 --- a/lib/modules/cloudmessaging.js +++ b/lib/modules/cloudmessaging.js @@ -1,12 +1,20 @@ -import {NativeModules, NativeEventEmitter} from 'react-native'; +import {Platform, NativeModules, NativeEventEmitter} from 'react-native'; const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); import promisify from '../utils/promisify' import { Base, ReferenceBase } from './base' + +const defaultPermissions = { + 'badge': 1, + 'sound': 2, + 'alert': 3 +} export class CloudMessaging extends Base { constructor(firestack, options = {}) { super(firestack, options); + + this.requestedPermissions = Object.assign({}, defaultPermissions, options.permissions); } get namespace() { return 'firestack:cloudMessaging' @@ -16,6 +24,20 @@ export class CloudMessaging extends Base { return promisify('getToken', FirestackCloudMessaging)(); } + // Request FCM permissions + requestPermissions(requestedPermissions = {}) { + if (Platform.OS === 'ios') { + const mergedRequestedPermissions = Object.assign({}, + this.requestedPermissions, + requestedPermissions); + return promisify('requestPermissions', FirestackCloudMessaging)(mergedRequestedPermissions) + .then(perms => { + + return perms; + }); + } + } + sendMessage(details:Object = {}, type:string='local') { const methodName = `send${type == 'local' ? 'Local' : 'Remote'}` this.log.info('sendMessage', methodName, details); From 98d873f26b1cbae4af9bed5c207d0adf385d9dc4 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Mon, 21 Nov 2016 17:04:44 -0500 Subject: [PATCH 20/23] Implement firestack.storage.download() for android --- .../fullstack/firestack/FirestackStorage.java | 156 ++++++++++++++++-- 1 file changed, 144 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index b41301d..a6b4094 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -5,6 +5,9 @@ import android.content.Context; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Map; import java.util.HashMap; @@ -26,6 +29,8 @@ import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.storage.StorageException; +import com.google.firebase.storage.StreamDownloadTask; import com.google.firebase.storage.UploadTask; import com.google.firebase.storage.FirebaseStorage; import com.google.firebase.storage.StorageMetadata; @@ -49,6 +54,18 @@ class FirestackStorageModule extends ReactContextBaseJavaModule { private static final String FileTypeRegular = "FILETYPE_REGULAR"; private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; + private static final String STORAGE_UPLOAD_PROGRESS = "upload_progress"; + private static final String STORAGE_UPLOAD_PAUSED = "upload_paused"; + private static final String STORAGE_UPLOAD_RESUMED = "upload_resumed"; + + private static final String STORAGE_DOWNLOAD_PROGRESS = "download_progress"; + private static final String STORAGE_DOWNLOAD_PAUSED = "download_paused"; + private static final String STORAGE_DOWNLOAD_RESUMED = "download_resumed"; + private static final String STORAGE_DOWNLOAD_SUCCESS = "download_success"; + private static final String STORAGE_DOWNLOAD_FAILURE = "download_failure"; + + private ReactContext mReactContext; + public FirestackStorageModule(ReactApplicationContext reactContext) { super(reactContext); @@ -60,6 +77,118 @@ public String getName() { return TAG; } + + public boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + if (Environment.MEDIA_MOUNTED.equals(state)) { + return true; + } + return false; + } + + @ReactMethod + public void downloadFile(final String urlStr, + final String fbPath, + final String localFile, + final Callback callback) { + Log.d(TAG, "downloadFile: "+urlStr+", "+localFile); + if (!isExternalStorageWritable()) { + Log.w(TAG, "downloadFile failed: external storage not writable"); + WritableMap error = Arguments.createMap(); + final int errorCode = 1; + error.putDouble("code", errorCode); + error.putString("description", "downloadFile failed: external storage not writable"); + callback.invoke(error); + return; + } + FirebaseStorage storage = FirebaseStorage.getInstance(); + String storageBucket = storage.getApp().getOptions().getStorageBucket(); + String storageUrl = "gs://" + storageBucket; + Log.d(TAG, "Storage url " + storageUrl + fbPath); + + StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); + StorageReference fileRef = storageRef.child(fbPath); + + fileRef.getStream(new StreamDownloadTask.StreamProcessor() { + @Override + public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot, InputStream inputStream) throws IOException { + int indexOfLastSlash = localFile.lastIndexOf("/"); + String pathMinusFileName = localFile.substring(0, indexOfLastSlash) + "/"; + String filename = localFile.substring(indexOfLastSlash+1); + File fileWithJustPath = new File(pathMinusFileName); + if (!fileWithJustPath.mkdirs()) { + Log.e(TAG, "Directory not created"); + WritableMap error = Arguments.createMap(); + error.putString("message", "Directory not created"); + callback.invoke(error); + return; + } + File fileWithFullPath = new File(pathMinusFileName, filename); + FileOutputStream output = new FileOutputStream(fileWithFullPath); + int bufferSize = 1024; + byte[] buffer = new byte[bufferSize]; + int len = 0; + while ((len = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, len); + } + output.close(); + } + }).addOnProgressListener(new OnProgressListener() { + @Override + public void onProgress(StreamDownloadTask.TaskSnapshot taskSnapshot) { + WritableMap data = Arguments.createMap(); + data.putString("ref", taskSnapshot.getStorage().getBucket()); + double percentComplete = taskSnapshot.getTotalByteCount() == 0 ? 0.0f : 100.0f * (taskSnapshot.getBytesTransferred()) / (taskSnapshot.getTotalByteCount()); + data.putDouble("progress", percentComplete); + FirestackUtils.sendEvent(mReactContext, STORAGE_DOWNLOAD_PROGRESS, data); + } + }).addOnPausedListener(new OnPausedListener() { + @Override + public void onPaused(StreamDownloadTask.TaskSnapshot taskSnapshot) { + WritableMap data = Arguments.createMap(); + data.putString("ref", taskSnapshot.getStorage().getBucket()); + FirestackUtils.sendEvent(mReactContext, STORAGE_DOWNLOAD_PAUSED, data); + } + }).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) { + final WritableMap data = Arguments.createMap(); + StorageReference ref = taskSnapshot.getStorage(); + data.putString("fullPath", ref.getPath()); + data.putString("bucket", ref.getBucket()); + data.putString("name", ref.getName()); + ref.getMetadata().addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(final StorageMetadata storageMetadata) { + data.putMap("metadata", getMetadataAsMap(storageMetadata)); + callback.invoke(null, data); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + final int errorCode = 1; + WritableMap data = Arguments.createMap(); + StorageException storageException = StorageException.fromException(exception); + data.putString("description", storageException.getMessage()); + data.putInt("code", errorCode); + callback.invoke(makeErrorPayload(errorCode, exception)); + } + }); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + final int errorCode = 1; + WritableMap data = Arguments.createMap(); + StorageException storageException = StorageException.fromException(exception); + data.putString("description", storageException.getMessage()); + data.putInt("code", errorCode); + callback.invoke(makeErrorPayload(errorCode, exception)); + } + }); + } + @ReactMethod public void downloadUrl(final String javascriptStorageBucket, final String path, @@ -90,16 +219,7 @@ public void onSuccess(Uri uri) { public void onSuccess(final StorageMetadata storageMetadata) { Log.d(TAG, "getMetadata success " + storageMetadata); - WritableMap metadata = Arguments.createMap(); - metadata.putString("getBucket", storageMetadata.getBucket()); - metadata.putString("getName", storageMetadata.getName()); - metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); - metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); - metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); - metadata.putString("md5hash", storageMetadata.getMd5Hash()); - metadata.putString("encoding", storageMetadata.getContentEncoding()); - - res.putMap("metadata", metadata); + res.putMap("metadata", getMetadataAsMap(storageMetadata)); res.putString("name", storageMetadata.getName()); res.putString("url", storageMetadata.getDownloadUrl().toString()); callback.invoke(null, res); @@ -129,6 +249,18 @@ public void onFailure(@NonNull Exception exception) { }); } + private WritableMap getMetadataAsMap(StorageMetadata storageMetadata) { + WritableMap metadata = Arguments.createMap(); + metadata.putString("getBucket", storageMetadata.getBucket()); + metadata.putString("getName", storageMetadata.getName()); + metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); + metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); + metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); + metadata.putString("md5hash", storageMetadata.getMd5Hash()); + metadata.putString("encoding", storageMetadata.getContentEncoding()); + return metadata; + } + // STORAGE @ReactMethod public void uploadFile(final String urlStr, final String name, final String filepath, final ReadableMap metadata, final Callback callback) { @@ -191,9 +323,9 @@ public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { if (progress >= 0) { WritableMap data = Arguments.createMap(); - data.putString("eventName", "upload_progress"); + data.putString("eventName", STORAGE_UPLOAD_PROGRESS); data.putDouble("progress", progress); - FirestackUtils.sendEvent(getReactApplicationContext(), "upload_progress", data); + FirestackUtils.sendEvent(getReactApplicationContext(), STORAGE_UPLOAD_PROGRESS, data); } } }) From f3233ade9f35ad12ef5224f400f159f6eefb9d02 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Mon, 21 Nov 2016 17:04:58 -0500 Subject: [PATCH 21/23] Factor out errorCodes for future constants --- .../io/fullstack/firestack/FirestackStorage.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index a6b4094..21ed217 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -229,7 +229,8 @@ public void onSuccess(final StorageMetadata storageMetadata) { @Override public void onFailure(@NonNull Exception exception) { Log.e(TAG, "Failure in download " + exception); - callback.invoke(makeErrorPayload(1, exception)); + final int errorCode = 1; + callback.invoke(makeErrorPayload(errorCode, exception)); } }); @@ -336,13 +337,14 @@ public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { StorageMetadata d = taskSnapshot.getMetadata(); String bucket = d.getBucket(); WritableMap data = Arguments.createMap(); - data.putString("eventName", "upload_paused"); + data.putString("eventName", STORAGE_UPLOAD_PAUSED); data.putString("ref", bucket); - FirestackUtils.sendEvent(getReactApplicationContext(), "upload_paused", data); + FirestackUtils.sendEvent(getReactApplicationContext(), STORAGE_UPLOAD_PAUSED, data); } }); } catch (Exception ex) { - callback.invoke(makeErrorPayload(2, ex)); + final int errorCode = 2; + callback.invoke(makeErrorPayload(errorCode, ex)); } } @@ -353,7 +355,8 @@ public void getRealPathFromURI(final String uri, final Callback callback) { callback.invoke(null, path); } catch (Exception ex) { ex.printStackTrace(); - callback.invoke(makeErrorPayload(1, ex)); + final int errorCode = 1; + callback.invoke(makeErrorPayload(errorCode, ex)); } } From d50e9766ee4f8089110ca7e0c7f3cf03325a7a6c Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Mon, 21 Nov 2016 18:19:04 -0500 Subject: [PATCH 22/23] Remove unused/old database.js Database.js was factored out into the database/ folder. This stuck around for some reason and the require('modules/database') preferred the old version --- lib/modules/database.js | 518 ---------------------------------------- 1 file changed, 518 deletions(-) delete mode 100644 lib/modules/database.js diff --git a/lib/modules/database.js b/lib/modules/database.js deleted file mode 100644 index cbf3bc4..0000000 --- a/lib/modules/database.js +++ /dev/null @@ -1,518 +0,0 @@ -/** - * Database representation wrapper - */ -import {NativeModules, NativeEventEmitter} from 'react-native'; -const FirestackDatabase = NativeModules.FirestackDatabase; -const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); - -import promisify from '../utils/promisify' -import { Base, ReferenceBase } from './base' - -let dbSubscriptions = {}; - -class DataSnapshot { - static key:String; - static value:Object; - static exists:boolean; - static hasChildren:boolean; - static childrenCount:Number; - static childKeys:String[]; - - constructor(ref, snapshot) { - this.ref = ref; - this.key = snapshot.key; - this.value = snapshot.value; - this.exists = snapshot.exists || true; - this.priority = snapshot.priority; - this.hasChildren = snapshot.hasChildren || false; - this.childrenCount = snapshot.childrenCount || 0; - this.childKeys = snapshot.childKeys || []; - } - - val() { - return this.value; - } - - forEach(fn) { - (this.childKeys || []) - .forEach(key => fn({key: key, value: this.value[key]})) - } - - map(fn) { - let arr = []; - this.forEach(item => arr.push(fn(item))) - return arr; - } - - reverseMap(fn) { - return this.map(fn).reverse(); - } -} - -class DatabaseOnDisconnect { - constructor(ref) { - this.ref = ref; - } - - setValue(val) { - const path = this.ref.dbPath(); - if (typeof val == 'string') { - return promisify('onDisconnectSetString', FirestackDatabase)(path, val); - } else if (typeof val == 'object') { - return promisify('onDisconnectSetObject', FirestackDatabase)(path, val); - } - } - - remove() { - const path = this.ref.dbPath(); - return promisify('onDisconnectRemove', FirestackDatabase)(path); - } - - cancel() { - const path = this.ref.dbPath(); - return promisify('onDisconnectCancel', FirestackDatabase)(path); - } -} - -class DatabaseQuery { - static ref: DatabaseRef; - static orderBy: String[]; - static limit: String[]; - static filters: Object; - - constructor(ref) { - this.ref = ref; - this.reset(); - } - - setOrderBy(name, ...args) { - this.orderBy = [name].concat(args); - return this.ref; - } - - setLimit(name, ...args) { - this.limit = [name].concat(args); - return this.ref; - } - - setFilter(name, ...args) { - this.filters[name] = args.filter(n => n != undefined); - return this.ref; - } - - build() { - const argsSeparator = ':' - let modifiers = []; - if (this.orderBy) { - modifiers.push(this.orderBy.join(argsSeparator)); - } - if (this.limit) { - modifiers.push(this.limit.join(argsSeparator)); - } - Object.keys(this.filters) - .forEach(key => { - let filter = this.filters[key]; - if (filter) { - const cleanFilters = filter.filter((f) => typeof f !== "undefined"); - const filterArgs = ([key].concat(cleanFilters)).join(argsSeparator); - modifiers.push(filterArgs); - } - }) - return modifiers; - } - - reset() { - this.orderBy = null; - this.limit = null; - this.filters = {}; - ['startAt', 'endAt', 'equalTo'] - .forEach(key => this.filters[key] = null); - return this.ref; - } -} - -// https://firebase.google.com/docs/reference/js/firebase.database.Reference -const separator = '/'; -class DatabaseRef extends ReferenceBase { - constructor(db, path) { - super(db.firestack, path); - - this.db = db; - this.query = new DatabaseQuery(this); - this.listeners = {}; - - // Aliases - this.get = this.getAt; - this.set = this.setAt; - this.update = this.updateAt; - this.remove = this.removeAt; - - this.log.debug('Created new DatabaseRef', this.dbPath()); - } - - // Parent roots - parent() { - const parentPaths = this.path.slice(0, -1); - return new DatabaseRef(this.db, parentPaths); - } - - root() { - return new DatabaseRef(this.db, []); - } - - child(...paths) { - return new DatabaseRef(this.db, this.path.concat(paths)); - } - - keepSynced(bool) { - const path = this.dbPath(); - return promisify('keepSynced', FirestackDatabase)(path, bool); - } - - // Get the value of a ref either with a key - getAt() { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, 'value'); - } - - setAt(val) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('set', FirestackDatabase)(path, value) - } - - updateAt(val) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('update', FirestackDatabase)(path, value) - } - - removeAt(key) { - const path = this.dbPath(); - return promisify('remove', FirestackDatabase)(path) - } - - push(val={}) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('push', FirestackDatabase)(path, value) - .then(({ref}) => { - return new DatabaseRef(this.db, ref.split(separator)) - }) - } - - on(evt, cb) { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return this.db.on(path, evt, cb) - .then(({callback, subscriptions}) => { - return promisify('on', FirestackDatabase)(path, modifiers, evt) - .then(() => { - this.listeners[evt] = subscriptions; - callback(this); - return subscriptions; - }) - }); - } - - once(evt='once', cb) { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, evt) - .then(({snapshot}) => new DataSnapshot(this, snapshot)) - .then(snapshot => { - if (cb && typeof cb === 'function') { - cb(snapshot); - } - return snapshot; - }) - } - - off(evt='', origCB) { - const path = this.dbPath(); - return this.db.off(path, evt, origCB) - .then(({callback, subscriptions}) => { - if (dbSubscriptions[path] && dbSubscriptions[path][evt] && dbSubscriptions[path][evt].length > 0) { - return subscriptions; - } - - return promisify('off', FirestackDatabase)(path, evt) - .then(() => { - // subscriptions.forEach(sub => sub.remove()); - delete this.listeners[evt]; - callback(this); - return subscriptions; - }) - }) - .catch(err => { - console.error('Never get here', err); - }) - } - - cleanup() { - let promises = Object.keys(this.listeners) - .map(key => this.off(key)) - return Promise.all(promises); - } - - // Sanitize value - // As Firebase cannot store date objects. - _serializeValue(obj={}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.toISOString(); - } - return { - ...sum, - [key]: val - } - }, {}); - } - - _deserializeValue(obj={}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.getTime(); - } - return { - ...sum, - [key]: val - } - }, {}); - } - - // Modifiers - orderByKey() { - return this.query.setOrderBy('orderByKey'); - } - - orderByPriority() { - return this.query.setOrderBy('orderByPriority'); - } - - orderByValue() { - return this.query.setOrderBy('orderByValue'); - } - - orderByChild(key) { - return this.query.setOrderBy('orderByChild', key); - } - - // Limits - limitToLast(limit) { - return this.query.setLimit('limitToLast', limit); - } - - limitToFirst(limit) { - return this.query.setLimit('limitToFirst', limit); - } - - // Filters - equalTo(value, key) { - return this.query.setFilter('equalTo', value, key); - } - - endAt(value, key) { - return this.query.setFilter('endAt', value, key); - } - - startAt(value, key) { - return this.query.setFilter('startAt', value, key); - } - - presence(path) { - const presence = this.firestack.presence; - const ref = path ? this.child(path) : this; - return presence.ref(ref, this.dbPath()); - } - - // onDisconnect - onDisconnect() { - return new DatabaseOnDisconnect(this); - } - - // attributes - get fullPath() { - return this.dbPath(); - } - - get name() { - return this.path.splice(-1); - } - - dbPath() { - let path = this.path; - let pathStr = (path.length > 0 ? path.join('/') : '/'); - if (pathStr[0] != '/') { - pathStr = `/${pathStr}` - } - return pathStr; - } - - dbModifiers() { - const modifiers = this.query.build(); - this.query.reset(); // should we reset this - return modifiers; - } - - get namespace() { - return `firestack:dbRef` - } -} - -export class Database extends Base { - - constructor(firestack, options={}) { - super(firestack, options); - this.log.debug('Created new Database instance', this.options); - - this.persistenceEnabled = false; - this.successListener = null; - this.errorListener = null; - this.refs = {}; - } - - ref(...path) { - const key = this._pathKey(path); - if (!this.refs[key]) { - const ref = new DatabaseRef(this, path); - this.refs[key] = ref; - } - return this.refs[key]; - } - - setPersistence(enable=true) { - let promise; - if (this.persistenceEnabled !== enable) { - this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`); - promise = this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable)); - this.persistenceEnabled = enable; - } else { - promise = this.whenReady(Promise.resolve({status: "Already enabled"})) - } - - return promise; - } - - handleDatabaseEvent(evt) { - const body = evt.body; - const path = body.path; - const evtName = body.eventName; - - const subscriptions = dbSubscriptions[path]; - - if (subscriptions) { - const cbs = subscriptions[evtName]; - cbs.forEach(cb => { - if (cb && typeof(cb) === 'function') { - const snap = new DataSnapshot(this, body.snapshot); - this.log.debug('database_event received', path, evtName); - cb(snap, body); - } - }); - } - } - - handleDatabaseError(evt) { - this.log.debug('handleDatabaseError ->', evt); - } - - on(path, evt, cb) { - const key = this._pathKey(path); - - if (!dbSubscriptions[key]) { - dbSubscriptions[key] = {}; - } - - if (!dbSubscriptions[key][evt]) { - dbSubscriptions[key][evt] = []; - } - dbSubscriptions[key][evt].push(cb); - - if (!this.successListener) { - this.successListener = FirestackDatabaseEvt - .addListener( - 'database_event', - this.handleDatabaseEvent.bind(this)); - } - - if (!this.errorListener) { - this.errorListener = FirestackDatabaseEvt - .addListener( - 'database_error', - this.handleDatabaseError.bind(this)); - } - - const callback = (ref) => { - const key = this._pathKey(ref.path); - this.refs[key] = ref; - } - const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); - } - - off(path, evt, origCB) { - const key = this._pathKey(path); - // Remove subscription - if (dbSubscriptions[key]) { - if (!evt || evt === "") { - dbSubscriptions[key] = {}; - } else if (dbSubscriptions[key][evt]) { - if (origCB) { - dbSubscriptions[key][evt].splice(dbSubscriptions[key][evt].indexOf(origCB), 1); - } else { - delete dbSubscriptions[key][evt]; - } - } - - if (Object.keys(dbSubscriptions[key]).length <= 0) { - // there are no more subscriptions - // so we can unwatch - delete dbSubscriptions[key] - } - if (Object.keys(dbSubscriptions).length == 0) { - if (this.successListener) { - this.successListener.remove(); - this.successListener = null; - } - if (this.errorListener) { - this.errorListener.remove(); - this.errorListener = null; - } - } - } - const callback = (ref) => { - const key = this._pathKey(ref.path); - delete this.refs[key]; - } - const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); - } - - cleanup() { - let promises = Object.keys(this.refs) - .map(key => this.refs[key]) - .map(ref => ref.cleanup()) - return Promise.all(promises); - } - - release(...path) { - const key = this._pathKey(path); - if (this.refs[key]) { - delete this.refs[key]; - } - } - - _pathKey(...path) { - return path.join('-'); - } - - get namespace() { - return 'firestack:database' - } -} - -export default Database From 4bb951c01b0191003da00152ab3bab3114ac59c8 Mon Sep 17 00:00:00 2001 From: tretelny Date: Fri, 25 Nov 2016 23:36:35 -0800 Subject: [PATCH 23/23] FCM implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most of the FCM implementation, local notifications could be cleaner… --- docs/api/cloud-messaging.md | 56 +++ ios/Firestack/Firestack.m | 4 +- ios/Firestack/FirestackCloudMessaging.h | 3 +- ios/Firestack/FirestackCloudMessaging.m | 434 ++++++++++++++---------- lib/modules/messaging.js | 44 ++- 5 files changed, 362 insertions(+), 179 deletions(-) diff --git a/docs/api/cloud-messaging.md b/docs/api/cloud-messaging.md index 8b13789..f53b217 100644 --- a/docs/api/cloud-messaging.md +++ b/docs/api/cloud-messaging.md @@ -1 +1,57 @@ +#cloud messaging +Make this prettier at some point but can't forget these things +setup certificates, enable push settings in app +Add things to app delegate +appdelegate.h -> +@import UserNotifications; +@interface AppDelegate : UIResponder + +appdelegate.m +Appdidfinishwithlaunching blah blah + UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey]; + NSDictionary *userInfo = [launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; + + if (localNotification) { + [[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_MESSAGE_RECEIVED_LOCAL object:localNotification]; + NSLog(@"fresh launch from local notificaiton"); + } + + if(userInfo){ + [[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_MESSAGE_RECEIVED_REMOTE object:self userInfo:userInfo]; + NSLog(@"fresh launch from remote"); + } + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { + // If you are receiving a notification message while your app is in the background, + // this callback will not be fired till the user taps on the notification launching the application. + // TODO: Handle data of notification + + // Print full message. + NSLog(@"%@", userInfo); + + [[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_MESSAGE_RECEIVED_REMOTE object:self userInfo:userInfo]; + +} + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler{ + NSLog(@"%@", userInfo); + if ( application.applicationState == UIApplicationStateActive ){ + //user had the app in the foreground + } + else { + //app went from background to foreground + } + [[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_MESSAGE_RECEIVED_REMOTE object:self userInfo:userInfo]; + completionHandler(UIBackgroundFetchResultNoData); +} + +- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification +{ + NSLog(@"%@", notification); + [[NSNotificationCenter defaultCenter] postNotificationName:MESSAGING_MESSAGE_RECEIVED_LOCAL object:notification]; +} + +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{ + NSLog(@"Notification Registration Error %@", [error description]); +} diff --git a/ios/Firestack/Firestack.m b/ios/Firestack/Firestack.m index 284f38b..7073fe8 100644 --- a/ios/Firestack/Firestack.m +++ b/ios/Firestack/Firestack.m @@ -8,7 +8,7 @@ #import "FirestackErrors.h" #import "FirestackEvents.h" // #import "FirestackAnalytics.h" -// #import "FirestackCloudMessaging.h" +#import "FirestackCloudMessaging.h" static Firestack *_sharedInstance = nil; static dispatch_once_t onceToken; @@ -239,6 +239,8 @@ - (FIRApp *) firebaseApp // if (!self.configured) { + //If you want this method could replace all of the above for the option setting for firebase + //FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:plistPath]; if ([FIRApp defaultApp] == NULL) { [FIRApp configureWithOptions:finalOptions]; } diff --git a/ios/Firestack/FirestackCloudMessaging.h b/ios/Firestack/FirestackCloudMessaging.h index 3e7c98b..52cbb7e 100644 --- a/ios/Firestack/FirestackCloudMessaging.h +++ b/ios/Firestack/FirestackCloudMessaging.h @@ -14,6 +14,7 @@ #import "RCTBridgeModule.h" #import "RCTUtils.h" + @interface FirestackCloudMessaging : RCTEventEmitter { } @@ -22,4 +23,4 @@ @end -#endif \ No newline at end of file +#endif diff --git a/ios/Firestack/FirestackCloudMessaging.m b/ios/Firestack/FirestackCloudMessaging.m index ea8a346..3d9f97a 100644 --- a/ios/Firestack/FirestackCloudMessaging.m +++ b/ios/Firestack/FirestackCloudMessaging.m @@ -13,153 +13,190 @@ #endif #import "FirestackCloudMessaging.h" #import "FirestackEvents.h" -// #import "RCTConvert.h" +#import "RCTConvert.h" + +// https://github.com/facebook/react-native/blob/master/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +@implementation RCTConvert (UILocalNotification) ++ (UILocalNotification *)UILocalNotification:(id)json +{ + NSDictionary *details = [self NSDictionary:json]; + + UILocalNotification* notification = [UILocalNotification new]; + notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]]; + notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; + notification.alertTitle = [RCTConvert NSString:details[@"alertTitle"]]; + notification.alertAction = [RCTConvert NSString:details[@"alertAction"]]; + notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; + notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]] ?: @{}; + notification.category = [RCTConvert NSString:details[@"category"]]; + + + return notification; +} +@end @implementation FirestackCloudMessaging // https://github.com/facebook/react-native/blob/master/Libraries/PushNotificationIOS/RCTPushNotificationManager.m static NSDictionary *RCTFormatLocalNotification(UILocalNotification *notification) { - NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; - if (notification.fireDate) { - NSDateFormatter *formatter = [NSDateFormatter new]; - [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; - NSString *fireDateString = [formatter stringFromDate:notification.fireDate]; - formattedLocalNotification[@"fireDate"] = fireDateString; - } - formattedLocalNotification[@"alertAction"] = RCTNullIfNil(notification.alertAction); - formattedLocalNotification[@"alertBody"] = RCTNullIfNil(notification.alertBody); - formattedLocalNotification[@"applicationIconBadgeNumber"] = @(notification.applicationIconBadgeNumber); - formattedLocalNotification[@"category"] = RCTNullIfNil(notification.category); - formattedLocalNotification[@"soundName"] = RCTNullIfNil(notification.soundName); - formattedLocalNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(notification.userInfo)); - formattedLocalNotification[@"remote"] = @NO; - return formattedLocalNotification; + NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; + if (notification.fireDate) { + NSDateFormatter *formatter = [NSDateFormatter new]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; + NSString *fireDateString = [formatter stringFromDate:notification.fireDate]; + formattedLocalNotification[@"fireDate"] = fireDateString; + } + formattedLocalNotification[@"alertAction"] = RCTNullIfNil(notification.alertAction); + formattedLocalNotification[@"alertBody"] = RCTNullIfNil(notification.alertBody); + formattedLocalNotification[@"applicationIconBadgeNumber"] = @(notification.applicationIconBadgeNumber); + formattedLocalNotification[@"category"] = RCTNullIfNil(notification.category); + formattedLocalNotification[@"soundName"] = RCTNullIfNil(notification.soundName); + formattedLocalNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(notification.userInfo)); + formattedLocalNotification[@"remote"] = @NO; + return formattedLocalNotification; } + - (void) dealloc { - [[NSNotificationCenter defaultCenter] removeObserver: self]; + [[NSNotificationCenter defaultCenter] removeObserver: self]; } -+ (void) setup:(UIApplication *) application +@synthesize bridge = _bridge; + +-(void) setBridge:(RCTBridge *)bridge { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(connectToFirebase) - name: UIApplicationDidEnterBackgroundNotification - object: nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(disconnectFromFirebase) - name: UIApplicationDidBecomeActiveNotification - object: nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleRemoteNotificationReceived:) - name:MESSAGING_MESSAGE_RECEIVED_REMOTE - object: nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleLocalNotificationReceived:) - name:MESSAGING_MESSAGE_RECEIVED_LOCAL - object: nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleTokenRefresh) - name:kFIRInstanceIDTokenRefreshNotification - object: nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(connectToFirebase) + name: UIApplicationDidBecomeActiveNotification + object: nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(disconnectFromFirebase) + name: UIApplicationDidEnterBackgroundNotification + object: nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleLocalNotificationReceived:) + name:MESSAGING_MESSAGE_RECEIVED_REMOTE + object: nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleLocalNotificationReceived:) + name:MESSAGING_MESSAGE_RECEIVED_LOCAL + object: nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleTokenRefresh) + name:kFIRInstanceIDTokenRefreshNotification + object: nil]; + + [[NSNotificationCenter defaultCenter]addObserver:self + selector:@selector(sendDataMessageFailure:) + name:FIRMessagingSendErrorNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sendDataMessageSuccess:) + name:FIRMessagingSendSuccessNotification + object:nil]; } + #pragma mark Request permissions -- (void) requestPermissions(NSDictionary *)requestedPermissions - callback:(RCTResponseSenderBlock) callback +RCT_EXPORT_METHOD(requestPermissions:(RCTResponseSenderBlock) callback) { - if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"9.0")) { - UIUserNotificationType allNotificationTypes = - (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); - UIUserNotificationSettings *settings = - [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil]; - [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; - } else { - // iOS 10 or later - #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - UNAuthorizationOptions authOptions = + if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"9.0")) { + UIUserNotificationType allNotificationTypes = + (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); + UIUserNotificationSettings *settings = + [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil]; + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + } else { + // iOS 10 or later +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge; - [[UNUserNotificationCenter currentNotificationCenter] - requestAuthorizationWithOptions:authOptions - completionHandler:^(BOOL granted, NSError * _Nullable error) { - } - ]; - - // For iOS 10 display notification (sent via APNS) - [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; - // For iOS 10 data message (sent via FCM) - [[FIRMessaging messaging] setRemoteMessageDelegate:self]; - #endif - } - - [[UIApplication sharedApplication] registerForRemoteNotifications]; + [[UNUserNotificationCenter currentNotificationCenter] + requestAuthorizationWithOptions:authOptions + completionHandler:^(BOOL granted, NSError * _Nullable error) { + } + ]; + + // For iOS 10 display notification (sent via APNS) + [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; + // For iOS 10 data message (sent via FCM) + //This is hacky + dispatch_async(dispatch_get_main_queue(), ^{ + [[FIRMessaging messaging] setRemoteMessageDelegate:self]; + }); +#endif + } + + [[UIApplication sharedApplication] registerForRemoteNotifications]; } #pragma mark callbacks - (void) connectToFirebase { - [[FIRMessaging messaging] connectWithCompletion:^(NSError *error) { - NSDictionary *evt; - NSString *evtName; - if (error != nil) { - NSLog(@"Error connecting: %@", [error debugDescription]); - evtName = MESSAGING_SUBSYSTEM_ERROR; - evt = @{ - @"eventName": MESSAGING_SUBSYSTEM_ERROR, - @"msg": [error debugDescription] - }; - } else { - NSLog(@"Connected to Firebase messaging"); - evtName = MESSAGING_SUBSYSTEM_EVENT; - evt = @{ - @"result": @"connected" - }; - [self - sendJSEvent:evtName - props: evt]; - - } - }]; + [[FIRMessaging messaging] connectWithCompletion:^(NSError *error) { + NSDictionary *evt; + NSString *evtName; + if (error != nil) { + NSLog(@"Error connecting: %@", [error debugDescription]); + evtName = MESSAGING_SUBSYSTEM_ERROR; + evt = @{ + @"eventName": MESSAGING_SUBSYSTEM_ERROR, + @"msg": [error debugDescription] + }; + } else { + NSLog(@"Connected to Firebase messaging"); + evtName = MESSAGING_SUBSYSTEM_EVENT; + evt = @{ + @"result": @"connected" + }; + [self + sendJSEvent:evtName + props: evt]; + + } + }]; } - (void) disconnectFromFirebase { - [[FIRMessaging messaging] disconnect]; - NSLog(@"Disconnect from Firebase"); - [self + [[FIRMessaging messaging] disconnect]; + NSLog(@"Disconnect from Firebase"); + [self sendJSEvent:MESSAGING_SUBSYSTEM_EVENT props: @{ - @"status": @"disconnected" - }]; + @"status": @"disconnected" + }]; } - (void) handleRemoteNotificationReceived:(NSNotification *) n { - NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithDictionary: n.userInfo]; - [self sendJSEvent:MESSAGING_MESSAGE_RECEIVED_REMOTE props: props]; + NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithDictionary: n.userInfo]; + [self sendJSEvent:MESSAGING_MESSAGE_RECEIVED_REMOTE props: n]; } - (void) handleLocalNotificationReceived:(NSNotification *) n { - NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithDictionary: n.userInfo]; - [self sendJSEvent:MESSAGING_MESSAGE_RECEIVED_LOCAL props: props]; + NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithDictionary: n.userInfo]; + [self sendJSEvent:MESSAGING_MESSAGE_RECEIVED_LOCAL props: props]; } + - (void) handleTokenRefresh { - NSDictionary *props = @{ - @"status": @"token_refreshed", - @"token": [[FIRInstanceID instanceID] token] - }; - [self sendJSEvent:MESSAGING_TOKEN_REFRESH props: props]; + NSDictionary *props = @{ + @"status": @"token_refreshed", + @"token": [[FIRInstanceID instanceID] token] + }; + [self sendJSEvent:MESSAGING_TOKEN_REFRESH props: props]; } RCT_EXPORT_MODULE(FirestackCloudMessaging); @@ -167,68 +204,121 @@ - (void) handleTokenRefresh RCT_EXPORT_METHOD(getToken:(RCTResponseSenderBlock)callback) { - NSString *token = [[FIRInstanceID instanceID] token]; - callback(@[[NSNull null], @{ - @"status": @"success", - @"token": token - }]); + NSString *token = [[FIRInstanceID instanceID] token]; + callback(@[[NSNull null], @{ + @"status": @"success", + @"token": token + }]); } +RCT_EXPORT_METHOD(getInitialNotification:(RCTResponseSenderBlock)callback){ + NSDictionary *localUserInfo = _bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; + if(localUserInfo){ + callback(@[[NSNull null], @{ + @"status": @"success", + @"launchedFrom": localUserInfo + }]); + } +} +//WORK ON THESE SOME MORE THIS IS UGLY -RCT_EXPORT_METHOD(sendLocal:(UILocalNotification *)notification - callback:(RCTResponseSenderBlock) callback) +RCT_EXPORT_METHOD(sendLocal:(NSDictionary *) notification + callback:(RCTResponseSenderBlock) callback) { - NSLog(@"sendLocal called with notification: %@", notification); - [RCTSharedApplication() presentLocalNotificationNow:notification]; + UILocalNotification* localNotification = [RCTConvert UILocalNotification:notification]; + NSLog(@"sendLocal called with notification: %@", notification); + [RCTSharedApplication() presentLocalNotificationNow:localNotification]; + } -RCT_EXPORT_METHOD(scheduleLocal:(UILocalNotification *)notification - callback:(RCTResponseSenderBlock) callback) +RCT_EXPORT_METHOD(scheduleLocal:(NSDictionary *) notification + callback:(RCTResponseSenderBlock) callback) { - [RCTSharedApplication() scheduleLocalNotification:notification]; + UILocalNotification* localNotification = [RCTConvert UILocalNotification:notification]; + NSLog(@"scheduleLocal called with notification: %@", notification); + [RCTSharedApplication() scheduleLocalNotification:localNotification]; } RCT_EXPORT_METHOD(cancelAllLocalNotifications) { - [RCTSharedApplication() cancelAllLocalNotifications]; + [RCTSharedApplication() cancelAllLocalNotifications]; } RCT_EXPORT_METHOD(cancelLocalNotifications:(NSDictionary *)userInfo) { - for (UILocalNotification *notification in [UIApplication sharedApplication].scheduledLocalNotifications) { - __block BOOL matchesAll = YES; - NSDictionary *notificationInfo = notification.userInfo; - // Note: we do this with a loop instead of just `isEqualToDictionary:` - // because we only require that all specified userInfo values match the - // notificationInfo values - notificationInfo may contain additional values - // which we don't care about. - [userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { - if (![notificationInfo[key] isEqual:obj]) { - matchesAll = NO; - *stop = YES; - } - }]; - if (matchesAll) { - [[UIApplication sharedApplication] cancelLocalNotification:notification]; + for (UILocalNotification *notification in [UIApplication sharedApplication].scheduledLocalNotifications) { + __block BOOL matchesAll = YES; + NSDictionary *notificationInfo = notification.userInfo; + // Note: we do this with a loop instead of just `isEqualToDictionary:` + // because we only require that all specified userInfo values match the + // notificationInfo values - notificationInfo may contain additional values + // which we don't care about. + [userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + if (![notificationInfo[key] isEqual:obj]) { + matchesAll = NO; + *stop = YES; + } + }]; + if (matchesAll) { + [[UIApplication sharedApplication] cancelLocalNotification:notification]; + } } - } } -RCT_EXPORT_METHOD(sendRemote:(UILocalNotification *)notification - callback:(RCTResponseSenderBlock) callback) +//RCT_EXPORT_METHOD(sendRemote:(UILocalNotification *)notification +// callback:(RCTResponseSenderBlock) callback) +//{ +// +//} + + +//for upstream it makes sense, we could probably use this as a general function for all local,upstream etc. Lets revisit +RCT_EXPORT_METHOD(send:(NSString *) messageId + msg: (NSDictionary *) msg + ttl: (int) ttl + callback:(RCTResponseSenderBlock)callback) { - + + int64_t ttl64 = (int64_t) ttl; + //This is hacky + dispatch_async(dispatch_get_main_queue(), ^{ + NSString* senderId = [[[FIRApp defaultApp] options] GCMSenderID]; + + senderId = [NSString stringWithFormat:@"%@@gcm.googleapis.com", senderId]; + [[FIRMessaging messaging] sendMessage:msg + to:senderId + withMessageID:messageId + timeToLive:ttl]; + }); } - -RCT_EXPORT_METHOD(send:(NSString *) senderId - messageId:(NSString *) messageId - messageType:(NSString *) messageType - msg: (NSString *) msg - callback:(RCTResponseSenderBlock)callback) +RCT_EXPORT_METHOD(sendMessage:(NSDictionary *) details + type: (NSString *) type + callback:(RCTResponseSenderBlock)callback) { + +// int64_t ttl64 = (int64_t) ttl; +// //This is hacky +// dispatch_async(dispatch_get_main_queue(), ^{ +// NSString* senderId = [[[FIRApp defaultApp] options] GCMSenderID]; +// +// senderId = [NSString stringWithFormat:@"%@@gcm.googleapis.com", senderId]; +// [[FIRMessaging messaging] sendMessage:msg +// to:senderId +// withMessageID:messageId +// timeToLive:ttl]; +// }); +} + +//send upstream always returns sendDataMessageFailure - pretty sure have to setup the server side component of this to actually see it work +- (void)sendDataMessageFailure:(NSNotification *)notification { + NSString *messageID = (NSString *)notification.userInfo[@"messageId"]; + NSDictionary *userInfo = notification.userInfo; // contains error info etc } +- (void)sendDataMessageSuccess:(NSNotification *)notification { + NSString *messageID = (NSString *)notification.userInfo[@"messageId"]; +} RCT_EXPORT_METHOD(listenForTokenRefresh:(RCTResponseSenderBlock)callback) {} @@ -237,41 +327,41 @@ - (void) handleTokenRefresh {} RCT_EXPORT_METHOD(subscribeToTopic:(NSString *) topic - callback:(RCTResponseSenderBlock)callback) + callback:(RCTResponseSenderBlock)callback) { - [[FIRMessaging messaging] subscribeToTopic:topic]; - callback(@[[NSNull null], @{ - @"result": @"success", - @"topic": topic - }]); + [[FIRMessaging messaging] subscribeToTopic:topic]; + callback(@[[NSNull null], @{ + @"result": @"success", + @"topic": topic + }]); } RCT_EXPORT_METHOD(unsubscribeFromTopic:(NSString *) topic - callback: (RCTResponseSenderBlock)callback) + callback: (RCTResponseSenderBlock)callback) { - [[FIRMessaging messaging] unsubscribeFromTopic:topic]; - callback(@[[NSNull null], @{ - @"result": @"success", - @"topic": topic - }]); + [[FIRMessaging messaging] unsubscribeFromTopic:topic]; + callback(@[[NSNull null], @{ + @"result": @"success", + @"topic": topic + }]); } RCT_EXPORT_METHOD(setBadge:(NSInteger) number - callback:(RCTResponseSenderBlock) callback) + callback:(RCTResponseSenderBlock) callback) { - RCTSharedApplication().applicationIconBadgeNumber = number; - callback(@[[NSNull null], @{ - @"result": @"success", - @"number": @(number) - }]); + RCTSharedApplication().applicationIconBadgeNumber = number; + callback(@[[NSNull null], @{ + @"result": @"success", + @"number": @(number) + }]); } RCT_EXPORT_METHOD(getBadge:(RCTResponseSenderBlock) callback) { - callback(@[[NSNull null], @{ - @"result": @"success", - @"number": @(RCTSharedApplication().applicationIconBadgeNumber) - }]); + callback(@[[NSNull null], @{ + @"result": @"success", + @"number": @(RCTSharedApplication().applicationIconBadgeNumber) + }]); } RCT_EXPORT_METHOD(listenForReceiveNotification:(RCTResponseSenderBlock)callback) @@ -289,11 +379,11 @@ - (void) handleTokenRefresh // Not sure how to get away from this... yet - (NSArray *)supportedEvents { return @[ - MESSAGING_SUBSYSTEM_EVENT, - MESSAGING_SUBSYSTEM_ERROR, - MESSAGING_TOKEN_REFRESH, - MESSAGING_MESSAGE_RECEIVED_LOCAL, - MESSAGING_MESSAGE_RECEIVED_REMOTE]; + MESSAGING_SUBSYSTEM_EVENT, + MESSAGING_SUBSYSTEM_ERROR, + MESSAGING_TOKEN_REFRESH, + MESSAGING_MESSAGE_RECEIVED_LOCAL, + MESSAGING_MESSAGE_RECEIVED_REMOTE]; } - (void) sendJSEvent:(NSString *)title @@ -302,9 +392,9 @@ - (void) sendJSEvent:(NSString *)title @try { [self sendEventWithName:title body:@{ - @"eventName": title, - @"body": props - }]; + @"eventName": title, + @"body": props + }]; } @catch (NSException *err) { NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); diff --git a/lib/modules/messaging.js b/lib/modules/messaging.js index 877cf29..696e5e4 100644 --- a/lib/modules/messaging.js +++ b/lib/modules/messaging.js @@ -5,14 +5,42 @@ import promisify from '../utils/promisify'; const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); +const defaultPermissions = { + 'badge': 1, + 'sound': 2, + 'alert': 3 +} + /** * @class Messaging */ export default class Messaging extends Base { constructor(firestack, options = {}) { super(firestack, options); - } - + //this.requestedPermissions = Object.assign({}, defaultPermissions, options.permissions); + + } + + // Request FCM permissions + // requestPermissions(requestedPermissions = {}) { + // // if (Platform.OS === 'ios') { + // const mergedRequestedPermissions = Object.assign({}, + // this.requestedPermissions, + // requestedPermissions); + // return promisify('requestPermissions', FirestackCloudMessaging)(mergedRequestedPermissions) + // .then(perms => { + + // return perms; + // }); + // // } + // } + + // Request FCM permissions + requestPermissions() { + this.log.info('requesting permissions'); + return promisify('requestPermissions', FirestackCloudMessaging)(); + // } + } /* * WEB API */ @@ -48,8 +76,14 @@ export default class Messaging extends Base { return promisify('getToken', FirestackCloudMessaging)(); } + getInitialNotification(){ + this.log.info('user launched app from notification'); + return promisify('getInitialNotification',FirestackCloudMessaging)(); + } + sendMessage(details: Object = {}, type: string = 'local') { const methodName = `send${type == 'local' ? 'Local' : 'Remote'}`; + this.log.info(methodName); this.log.info('sendMessage', methodName, details); return promisify(methodName, FirestackCloudMessaging)(details); } @@ -60,8 +94,8 @@ export default class Messaging extends Base { } // OLD - send(senderId, messageId, messageType, msg) { - return promisify('send', FirestackCloudMessaging)(senderId, messageId, messageType, msg); + send(messageId, msg,ttl) { + return promisify('send', FirestackCloudMessaging)(messageId, msg,ttl); } // @@ -111,7 +145,7 @@ export default class Messaging extends Base { console.warn('Firestack: listenForReceiveNotification is now deprecated, please use onMessage'); return this.onMessage(...args); } - + /** * @deprecated * @param args