mirror of
https://github.com/zhigang1992/react-native-firebase.git
synced 2026-06-19 10:08:58 +08:00
RNFirebase android
This commit is contained in:
10
android/.editorconfig
Normal file
10
android/.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
56
android/build.gradle
Normal file
56
android/build.gradle
Normal file
@@ -0,0 +1,56 @@
|
||||
// START - required to allow working on this project inside Android Studio
|
||||
// YES, jcenter is required twice - it somehow tricks studio into compiling deps below
|
||||
// doesn't break anything anywhere else and projects using this lib work as normal
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.1.3'
|
||||
}
|
||||
}
|
||||
// END
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 23
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
multiDexEnabled true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// START - required to allow working on this project inside Android Studio
|
||||
// YES, jcenter is required twice - it somehow tricks studio into compiling deps below
|
||||
// doesn't break anything anywhere else and projects using this lib work as normal
|
||||
// you'll now have code completion/validation and all the other AS goodies.
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
// END
|
||||
|
||||
dependencies {
|
||||
compile 'com.facebook.react:react-native:0.20.+'
|
||||
compile 'com.google.android.gms:play-services-base:9.8.0'
|
||||
compile 'com.google.firebase:firebase-core:9.8.0'
|
||||
compile 'com.google.firebase:firebase-config:9.8.0'
|
||||
compile 'com.google.firebase:firebase-auth:9.8.0'
|
||||
compile 'com.google.firebase:firebase-analytics:9.8.0'
|
||||
compile 'com.google.firebase:firebase-database:9.8.0'
|
||||
compile 'com.google.firebase:firebase-storage:9.8.0'
|
||||
compile 'com.google.firebase:firebase-messaging:9.8.0'
|
||||
}
|
||||
|
||||
3
android/src/main/AndroidManifest.xml
Normal file
3
android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.invertase.firebase">
|
||||
</manifest>
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.invertase.firebase;
|
||||
|
||||
import android.util.Log;
|
||||
import android.os.Bundle;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
import com.google.firebase.iid.FirebaseInstanceIdService;
|
||||
|
||||
import io.invertase.firebase.messaging.RNFirebaseMessaging;
|
||||
|
||||
public class RNFirebaseInstanceIdService extends FirebaseInstanceIdService {
|
||||
|
||||
private static final String TAG = "FSInstanceIdService";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void onTokenRefresh() {
|
||||
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
|
||||
Log.d(TAG, "Refreshed token: " + refreshedToken);
|
||||
Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_TOKEN);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("token", refreshedToken);
|
||||
i.putExtras(bundle);
|
||||
sendBroadcast(i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.invertase.firebase;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
import com.google.firebase.messaging.SendException;
|
||||
|
||||
import io.invertase.firebase.messaging.RNFirebaseMessaging;
|
||||
|
||||
public class RNFirebaseMessagingService extends FirebaseMessagingService {
|
||||
|
||||
private static final String TAG = "FSMessagingService";
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(RemoteMessage remoteMessage) {
|
||||
Log.d(TAG, "Remote message received");
|
||||
// debug
|
||||
Log.d(TAG, "From: " + remoteMessage.getFrom());
|
||||
|
||||
if (remoteMessage.getData().size() > 0) {
|
||||
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
|
||||
}
|
||||
|
||||
if (remoteMessage.getNotification() != null) {
|
||||
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
|
||||
}
|
||||
|
||||
Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_NOTIFICATION);
|
||||
i.putExtra("data", remoteMessage);
|
||||
sendOrderedBroadcast(i, null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageSent(String msgId) {
|
||||
// Called when an upstream message has been successfully sent to the GCM connection server.
|
||||
Log.d(TAG, "upstream message has been successfully sent");
|
||||
Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_SEND);
|
||||
i.putExtra("msgId", msgId);
|
||||
sendOrderedBroadcast(i, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendError(String msgId, Exception exception) {
|
||||
// Called when there was an error sending an upstream message.
|
||||
Log.d(TAG, "error sending an upstream message");
|
||||
Intent i = new Intent(RNFirebaseMessaging.INTENT_NAME_SEND);
|
||||
i.putExtra("msgId", msgId);
|
||||
i.putExtra("hasError", true);
|
||||
SendException sendException = (SendException) exception;
|
||||
i.putExtra("errorCode", sendException.getErrorCode());
|
||||
switch(sendException.getErrorCode()){
|
||||
case SendException.ERROR_INVALID_PARAMETERS:
|
||||
i.putExtra("errorMessage", "Message was sent with invalid parameters.");
|
||||
break;
|
||||
case SendException.ERROR_SIZE:
|
||||
i.putExtra("errorMessage", "Message exceeded the maximum payload size.");
|
||||
break;
|
||||
case SendException.ERROR_TOO_MANY_MESSAGES:
|
||||
i.putExtra("errorMessage", "App has too many pending messages so this one was dropped.");
|
||||
break;
|
||||
case SendException.ERROR_TTL_EXCEEDED:
|
||||
i.putExtra("errorMessage", "Message time to live (TTL) was exceeded before the message could be sent.");
|
||||
break;
|
||||
case SendException.ERROR_UNKNOWN:
|
||||
default:
|
||||
i.putExtra("errorMessage", "Unknown error.");
|
||||
break;
|
||||
}
|
||||
sendOrderedBroadcast(i, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package io.invertase.firebase;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.util.Log;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.FirebaseOptions;
|
||||
import com.google.firebase.database.ServerValue;
|
||||
|
||||
interface KeySetterFn {
|
||||
String setKeyOrDefault(String a, String b);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class RNFirebaseModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
|
||||
private static final String TAG = "RNFirebase";
|
||||
private FirebaseApp app;
|
||||
|
||||
public RNFirebaseModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private WritableMap getPlayServicesStatus() {
|
||||
GoogleApiAvailability gapi = GoogleApiAvailability.getInstance();
|
||||
final int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext());
|
||||
WritableMap result = Arguments.createMap();
|
||||
result.putInt("status", status);
|
||||
if (status == ConnectionResult.SUCCESS) {
|
||||
result.putBoolean("isAvailable", true);
|
||||
} else {
|
||||
result.putBoolean("isAvailable", false);
|
||||
result.putBoolean("isUserResolvableError", gapi.isUserResolvableError(status));
|
||||
result.putString("error", gapi.getErrorString(status));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void configureWithOptions(final ReadableMap params, @Nullable final Callback onComplete) {
|
||||
Log.i(TAG, "configureWithOptions");
|
||||
|
||||
FirebaseOptions.Builder builder = new FirebaseOptions.Builder();
|
||||
FirebaseOptions defaultOptions = FirebaseOptions.fromResource(getReactApplicationContext().getBaseContext());
|
||||
|
||||
if (defaultOptions == null) {
|
||||
defaultOptions = new FirebaseOptions.Builder().build();
|
||||
}
|
||||
|
||||
KeySetterFn fn = new KeySetterFn() {
|
||||
public String setKeyOrDefault(
|
||||
final String key,
|
||||
final String defaultValue) {
|
||||
if (params.hasKey(key)) {
|
||||
// User-set key
|
||||
final String val = params.getString(key);
|
||||
Log.d(TAG, "Setting " + key + " from params to: " + val);
|
||||
return val;
|
||||
} else if (defaultValue != null && !defaultValue.equals("")) {
|
||||
Log.d(TAG, "Setting " + key + " from params to: " + defaultValue);
|
||||
return defaultValue;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
String val = fn.setKeyOrDefault("applicationId", defaultOptions.getApplicationId());
|
||||
if (val != null) builder.setApplicationId(val);
|
||||
|
||||
val = fn.setKeyOrDefault("apiKey", defaultOptions.getApiKey());
|
||||
if (val != null) builder.setApiKey(val);
|
||||
|
||||
val = fn.setKeyOrDefault("gcmSenderID", defaultOptions.getGcmSenderId());
|
||||
if (val != null) builder.setGcmSenderId(val);
|
||||
|
||||
val = fn.setKeyOrDefault("storageBucket", defaultOptions.getStorageBucket());
|
||||
if (val != null) builder.setStorageBucket(val);
|
||||
|
||||
val = fn.setKeyOrDefault("databaseURL", defaultOptions.getDatabaseUrl());
|
||||
if (val != null) builder.setDatabaseUrl(val);
|
||||
|
||||
val = fn.setKeyOrDefault("databaseUrl", defaultOptions.getDatabaseUrl());
|
||||
if (val != null) builder.setDatabaseUrl(val);
|
||||
|
||||
val = fn.setKeyOrDefault("clientId", defaultOptions.getApplicationId());
|
||||
if (val != null) builder.setApplicationId(val);
|
||||
|
||||
|
||||
// if (params.hasKey("applicationId")) {
|
||||
// final String applicationId = params.getString("applicationId");
|
||||
// Log.d(TAG, "Setting applicationId from params " + applicationId);
|
||||
// builder.setApplicationId(applicationId);
|
||||
// }
|
||||
// if (params.hasKey("apiKey")) {
|
||||
// final String apiKey = params.getString("apiKey");
|
||||
// Log.d(TAG, "Setting API key from params " + apiKey);
|
||||
// builder.setApiKey(apiKey);
|
||||
// }
|
||||
// if (params.hasKey("APIKey")) {
|
||||
// final String apiKey = params.getString("APIKey");
|
||||
// Log.d(TAG, "Setting API key from params " + apiKey);
|
||||
// builder.setApiKey(apiKey);
|
||||
// }
|
||||
// if (params.hasKey("gcmSenderID")) {
|
||||
// final String gcmSenderID = params.getString("gcmSenderID");
|
||||
// Log.d(TAG, "Setting gcmSenderID from params " + gcmSenderID );
|
||||
// builder.setGcmSenderId(gcmSenderID);
|
||||
// }
|
||||
// if (params.hasKey("storageBucket")) {
|
||||
// final String storageBucket = params.getString("storageBucket");
|
||||
// Log.d(TAG, "Setting storageBucket from params " + storageBucket);
|
||||
// builder.setStorageBucket(storageBucket);
|
||||
// }
|
||||
// if (params.hasKey("databaseURL")) {
|
||||
// final String databaseURL = params.getString("databaseURL");
|
||||
// Log.d(TAG, "Setting databaseURL from params " + databaseURL);
|
||||
// builder.setDatabaseUrl(databaseURL);
|
||||
// }
|
||||
// if (params.hasKey("clientID")) {
|
||||
// final String clientID = params.getString("clientID");
|
||||
// Log.d(TAG, "Setting clientID from params " + clientID);
|
||||
// builder.setApplicationId(clientID);
|
||||
// }
|
||||
|
||||
try {
|
||||
Log.i(TAG, "Configuring app");
|
||||
if (app == null) {
|
||||
app = FirebaseApp.initializeApp(getReactApplicationContext().getBaseContext(), builder.build());
|
||||
}
|
||||
Log.i(TAG, "Configured");
|
||||
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("msg", "success");
|
||||
onComplete.invoke(null, resp);
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "ERROR configureWithOptions");
|
||||
Log.e(TAG, ex.getMessage());
|
||||
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("msg", ex.getMessage());
|
||||
|
||||
onComplete.invoke(resp);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void serverValue(@Nullable final Callback onComplete) {
|
||||
WritableMap timestampMap = Arguments.createMap();
|
||||
for (Map.Entry<String, String> entry : ServerValue.TIMESTAMP.entrySet()) {
|
||||
timestampMap.putString(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putMap("TIMESTAMP", timestampMap);
|
||||
if (onComplete != null) onComplete.invoke(null, map);
|
||||
}
|
||||
|
||||
// Internal helpers
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putBoolean("isForground", true);
|
||||
Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putBoolean("isForground", false);
|
||||
Utils.sendEvent(getReactApplicationContext(), "RNFirebaseAppState", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
final Map<String, Object> constants = new HashMap<>();
|
||||
constants.put("googleApiAvailability", getPlayServicesStatus());
|
||||
|
||||
// TODO remove once this has been moved on ios
|
||||
constants.put("serverValueTimestamp", ServerValue.TIMESTAMP);
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.invertase.firebase;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import io.invertase.firebase.auth.RNFirebaseAuth;
|
||||
import io.invertase.firebase.storage.RNFirebaseStorage;
|
||||
import io.invertase.firebase.database.RNFirebaseDatabase;
|
||||
import io.invertase.firebase.analytics.RNFirebaseAnalytics;
|
||||
import io.invertase.firebase.messaging.RNFirebaseMessaging;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RNFirebasePackage implements ReactPackage {
|
||||
private Context mContext;
|
||||
|
||||
public RNFirebasePackage() {
|
||||
}
|
||||
/**
|
||||
* @param reactContext react application context that can be used to create modules
|
||||
* @return list of native modules to register with the newly created catalyst instance
|
||||
*/
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
modules.add(new RNFirebaseModule(reactContext));
|
||||
modules.add(new RNFirebaseAuth(reactContext));
|
||||
modules.add(new RNFirebaseDatabase(reactContext));
|
||||
modules.add(new RNFirebaseAnalytics(reactContext));
|
||||
modules.add(new RNFirebaseStorage(reactContext));
|
||||
modules.add(new RNFirebaseMessaging(reactContext));
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of JS modules to register with the newly created catalyst instance.
|
||||
* <p/>
|
||||
* IMPORTANT: Note that only modules that needs to be accessible from the native code should be
|
||||
* listed here. Also listing a native module here doesn't imply that the JS implementation of it
|
||||
* will be automatically included in the JS bundle.
|
||||
*/
|
||||
@Override
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param reactContext
|
||||
* @return a list of view managers that should be registered with {@link UIManagerModule}
|
||||
*/
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
301
android/src/main/java/io/invertase/firebase/Utils.java
Normal file
301
android/src/main/java/io/invertase/firebase/Utils.java
Normal file
@@ -0,0 +1,301 @@
|
||||
package io.invertase.firebase;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
import com.facebook.react.bridge.ReadableType;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.google.firebase.database.DataSnapshot;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class Utils {
|
||||
private static final String TAG = "Utils";
|
||||
|
||||
// TODO NOTE
|
||||
public static void todoNote(final String tag, final String name, final Callback callback) {
|
||||
Log.e(tag, "The method " + name + " has not yet been implemented.");
|
||||
Log.e(tag, "Feel free to contribute to finish the method in the source.");
|
||||
|
||||
WritableMap errorMap = Arguments.createMap();
|
||||
errorMap.putString("error", "unimplemented");
|
||||
callback.invoke(null, errorMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* send a JS event
|
||||
**/
|
||||
public static void sendEvent(final ReactContext context, final String eventName, final WritableMap params) {
|
||||
if (context != null) {
|
||||
context
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(eventName, params);
|
||||
} else {
|
||||
Log.d(TAG, "Missing context - cannot send event!");
|
||||
}
|
||||
}
|
||||
|
||||
// snapshot
|
||||
public static WritableMap dataSnapshotToMap(
|
||||
String name,
|
||||
String path,
|
||||
String modifiersString,
|
||||
DataSnapshot dataSnapshot
|
||||
) {
|
||||
WritableMap data = Arguments.createMap();
|
||||
|
||||
data.putString("key", dataSnapshot.getKey());
|
||||
data.putBoolean("exists", dataSnapshot.exists());
|
||||
data.putBoolean("hasChildren", dataSnapshot.hasChildren());
|
||||
|
||||
data.putDouble("childrenCount", dataSnapshot.getChildrenCount());
|
||||
if (!dataSnapshot.hasChildren()) {
|
||||
Object value = dataSnapshot.getValue();
|
||||
String type = value != null ? value.getClass().getName() : "";
|
||||
switch (type) {
|
||||
case "java.lang.Boolean":
|
||||
data.putBoolean("value", (Boolean) value);
|
||||
break;
|
||||
case "java.lang.Long":
|
||||
Long longVal = (Long) value;
|
||||
data.putDouble("value", (double) longVal);
|
||||
break;
|
||||
case "java.lang.Double":
|
||||
data.putDouble("value", (Double) value);
|
||||
break;
|
||||
case "java.lang.String":
|
||||
data.putString("value", (String) value);
|
||||
break;
|
||||
default:
|
||||
data.putString("value", null);
|
||||
}
|
||||
} else {
|
||||
Object value = Utils.castSnapshotValue(dataSnapshot);
|
||||
if (value instanceof WritableNativeArray) {
|
||||
data.putArray("value", (WritableArray) value);
|
||||
} else {
|
||||
data.putMap("value", (WritableMap) value);
|
||||
}
|
||||
}
|
||||
|
||||
// Child keys
|
||||
WritableArray childKeys = Utils.getChildKeys(dataSnapshot);
|
||||
data.putArray("childKeys", childKeys);
|
||||
|
||||
Object priority = dataSnapshot.getPriority();
|
||||
if (priority == null) {
|
||||
data.putString("priority", null);
|
||||
} else {
|
||||
data.putString("priority", priority.toString());
|
||||
}
|
||||
|
||||
WritableMap eventMap = Arguments.createMap();
|
||||
eventMap.putString("eventName", name);
|
||||
eventMap.putMap("snapshot", data);
|
||||
eventMap.putString("path", path);
|
||||
eventMap.putString("modifiersString", modifiersString);
|
||||
return eventMap;
|
||||
}
|
||||
|
||||
public static <Any> Any castSnapshotValue(DataSnapshot snapshot) {
|
||||
if (snapshot.hasChildren()) {
|
||||
if (isArray(snapshot)) {
|
||||
return (Any) buildArray(snapshot);
|
||||
} else {
|
||||
return (Any) buildMap(snapshot);
|
||||
}
|
||||
} else {
|
||||
if (snapshot.getValue() != null) {
|
||||
String type = snapshot.getValue().getClass().getName();
|
||||
switch (type) {
|
||||
case "java.lang.Boolean":
|
||||
return (Any) (snapshot.getValue());
|
||||
case "java.lang.Long":
|
||||
return (Any) (snapshot.getValue());
|
||||
case "java.lang.Double":
|
||||
return (Any) (snapshot.getValue());
|
||||
case "java.lang.String":
|
||||
return (Any) (snapshot.getValue());
|
||||
default:
|
||||
Log.w(TAG, "Invalid type: " + type);
|
||||
return (Any) null;
|
||||
}
|
||||
}
|
||||
return (Any) null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isArray(DataSnapshot snapshot) {
|
||||
long expectedKey = 0;
|
||||
for (DataSnapshot child : snapshot.getChildren()) {
|
||||
try {
|
||||
long key = Long.parseLong(child.getKey());
|
||||
if (key == expectedKey) {
|
||||
expectedKey++;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static <Any> WritableArray buildArray(DataSnapshot snapshot) {
|
||||
WritableArray array = Arguments.createArray();
|
||||
for (DataSnapshot child : snapshot.getChildren()) {
|
||||
Any castedChild = castSnapshotValue(child);
|
||||
switch (castedChild.getClass().getName()) {
|
||||
case "java.lang.Boolean":
|
||||
array.pushBoolean((Boolean) castedChild);
|
||||
break;
|
||||
case "java.lang.Long":
|
||||
Long longVal = (Long) castedChild;
|
||||
array.pushDouble((double) longVal);
|
||||
break;
|
||||
case "java.lang.Double":
|
||||
array.pushDouble((Double) castedChild);
|
||||
break;
|
||||
case "java.lang.String":
|
||||
array.pushString((String) castedChild);
|
||||
break;
|
||||
case "com.facebook.react.bridge.WritableNativeMap":
|
||||
array.pushMap((WritableMap) castedChild);
|
||||
break;
|
||||
case "com.facebook.react.bridge.WritableNativeArray":
|
||||
array.pushArray((WritableArray) castedChild);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private static <Any> WritableMap buildMap(DataSnapshot snapshot) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
for (DataSnapshot child : snapshot.getChildren()) {
|
||||
Any castedChild = castSnapshotValue(child);
|
||||
|
||||
switch (castedChild.getClass().getName()) {
|
||||
case "java.lang.Boolean":
|
||||
map.putBoolean(child.getKey(), (Boolean) castedChild);
|
||||
break;
|
||||
case "java.lang.Long":
|
||||
Long longVal = (Long) castedChild;
|
||||
map.putDouble(child.getKey(), (double) longVal);
|
||||
break;
|
||||
case "java.lang.Double":
|
||||
map.putDouble(child.getKey(), (Double) castedChild);
|
||||
break;
|
||||
case "java.lang.String":
|
||||
map.putString(child.getKey(), (String) castedChild);
|
||||
break;
|
||||
case "com.facebook.react.bridge.WritableNativeMap":
|
||||
map.putMap(child.getKey(), (WritableMap) castedChild);
|
||||
break;
|
||||
case "com.facebook.react.bridge.WritableNativeArray":
|
||||
map.putArray(child.getKey(), (WritableArray) castedChild);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static WritableArray getChildKeys(DataSnapshot snapshot) {
|
||||
WritableArray childKeys = Arguments.createArray();
|
||||
|
||||
if (snapshot.hasChildren()) {
|
||||
for (DataSnapshot child : snapshot.getChildren()) {
|
||||
childKeys.pushString(child.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
return childKeys;
|
||||
}
|
||||
|
||||
public static Map<String, Object> recursivelyDeconstructReadableMap(ReadableMap readableMap) {
|
||||
Map<String, Object> deconstructedMap = new HashMap<>();
|
||||
if (readableMap == null) {
|
||||
return deconstructedMap;
|
||||
}
|
||||
|
||||
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
|
||||
while (iterator.hasNextKey()) {
|
||||
String key = iterator.nextKey();
|
||||
ReadableType type = readableMap.getType(key);
|
||||
switch (type) {
|
||||
case Null:
|
||||
deconstructedMap.put(key, null);
|
||||
break;
|
||||
case Boolean:
|
||||
deconstructedMap.put(key, readableMap.getBoolean(key));
|
||||
break;
|
||||
case Number:
|
||||
deconstructedMap.put(key, readableMap.getDouble(key));
|
||||
break;
|
||||
case String:
|
||||
deconstructedMap.put(key, readableMap.getString(key));
|
||||
break;
|
||||
case Map:
|
||||
deconstructedMap.put(key, Utils.recursivelyDeconstructReadableMap(readableMap.getMap(key)));
|
||||
break;
|
||||
case Array:
|
||||
deconstructedMap.put(key, Utils.recursivelyDeconstructReadableArray(readableMap.getArray(key)));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
|
||||
}
|
||||
|
||||
}
|
||||
return deconstructedMap;
|
||||
}
|
||||
|
||||
public static List<Object> recursivelyDeconstructReadableArray(ReadableArray readableArray) {
|
||||
List<Object> deconstructedList = new ArrayList<>(readableArray.size());
|
||||
for (int i = 0; i < readableArray.size(); i++) {
|
||||
ReadableType indexType = readableArray.getType(i);
|
||||
switch (indexType) {
|
||||
case Null:
|
||||
deconstructedList.add(i, null);
|
||||
break;
|
||||
case Boolean:
|
||||
deconstructedList.add(i, readableArray.getBoolean(i));
|
||||
break;
|
||||
case Number:
|
||||
deconstructedList.add(i, readableArray.getDouble(i));
|
||||
break;
|
||||
case String:
|
||||
deconstructedList.add(i, readableArray.getString(i));
|
||||
break;
|
||||
case Map:
|
||||
deconstructedList.add(i, Utils.recursivelyDeconstructReadableMap(readableArray.getMap(i)));
|
||||
break;
|
||||
case Array:
|
||||
deconstructedList.add(i, Utils.recursivelyDeconstructReadableArray(readableArray.getArray(i)));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Could not convert object at index " + i + ".");
|
||||
}
|
||||
}
|
||||
return deconstructedList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package io.invertase.firebase.analytics;
|
||||
|
||||
import android.util.Log;
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
|
||||
public class RNFirebaseAnalytics extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "RNFirebaseAnalytics";
|
||||
|
||||
public RNFirebaseAnalytics(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
Log.d(TAG, "New instance");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void logEvent(final String name, @Nullable final ReadableMap params) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).logEvent(name, Arguments.toBundle(params));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setAnalyticsCollectionEnabled(final Boolean enabled) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setAnalyticsCollectionEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param screenName
|
||||
* @param screenClassOverride
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setCurrentScreen(final String screenName, final String screenClassOverride) {
|
||||
final Activity activity = getCurrentActivity();
|
||||
if (activity != null) {
|
||||
// needs to be run on main thread
|
||||
Log.d(TAG, "setCurrentScreen " + screenName + " - " + screenClassOverride);
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setCurrentScreen(activity, screenName, screenClassOverride);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param milliseconds
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setMinimumSessionDuration(final double milliseconds) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setMinimumSessionDuration((long) milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param milliseconds
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setSessionTimeoutDuration(final double milliseconds) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setSessionTimeoutDuration((long) milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserId(final String id) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserProperty(final String name, final String value) {
|
||||
FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserProperty(name, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
|
||||
package io.invertase.firebase.auth;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
|
||||
import com.google.firebase.auth.AuthCredential;
|
||||
import com.google.firebase.auth.AuthResult;
|
||||
import com.google.firebase.auth.UserProfileChangeRequest;
|
||||
import com.google.firebase.auth.FacebookAuthProvider;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseAuthException;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
import com.google.firebase.auth.GetTokenResult;
|
||||
import com.google.firebase.auth.GoogleAuthProvider;
|
||||
import com.google.firebase.auth.EmailAuthProvider;
|
||||
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
|
||||
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
|
||||
public class RNFirebaseAuth extends ReactContextBaseJavaModule {
|
||||
private final int NO_CURRENT_USER = 100;
|
||||
private final int ERROR_FETCHING_TOKEN = 101;
|
||||
private final int ERROR_SENDING_VERIFICATION_EMAIL = 102;
|
||||
|
||||
private static final String TAG = "RNFirebaseAuth";
|
||||
|
||||
// private Context context;
|
||||
private ReactContext mReactContext;
|
||||
private FirebaseAuth mAuth;
|
||||
private FirebaseAuth.AuthStateListener mAuthListener;
|
||||
|
||||
public RNFirebaseAuth(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mReactContext = reactContext;
|
||||
mAuth = FirebaseAuth.getInstance();
|
||||
|
||||
Log.d(TAG, "New RNFirebaseAuth instance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a no user error.
|
||||
*
|
||||
* @param callback JS callback
|
||||
*/
|
||||
private void callbackNoUser(Callback callback, Boolean isError) {
|
||||
WritableMap err = Arguments.createMap();
|
||||
err.putInt("errorCode", NO_CURRENT_USER);
|
||||
err.putString("errorMessage", "No current user");
|
||||
|
||||
if (isError) {
|
||||
callback.invoke(err);
|
||||
} else {
|
||||
callback.invoke(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void listenForAuth() {
|
||||
if (mAuthListener == null) {
|
||||
mAuthListener = new FirebaseAuth.AuthStateListener() {
|
||||
@Override
|
||||
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
|
||||
FirebaseUser user = firebaseAuth.getCurrentUser();
|
||||
WritableMap msgMap = Arguments.createMap();
|
||||
msgMap.putString("eventName", "listenForAuth");
|
||||
|
||||
if (user != null) {
|
||||
// TODO move to helper
|
||||
WritableMap userMap = getUserMap(user);
|
||||
msgMap.putBoolean("authenticated", true);
|
||||
msgMap.putMap("user", userMap);
|
||||
|
||||
Utils.sendEvent(mReactContext, "listenForAuth", msgMap);
|
||||
} else {
|
||||
msgMap.putBoolean("authenticated", false);
|
||||
Utils.sendEvent(mReactContext, "listenForAuth", msgMap);
|
||||
}
|
||||
}
|
||||
};
|
||||
mAuth.addAuthStateListener(mAuthListener);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void unlistenForAuth(final Callback callback) {
|
||||
if (mAuthListener != null) {
|
||||
mAuth.removeAuthStateListener(mAuthListener);
|
||||
|
||||
// TODO move to helper
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("status", "complete");
|
||||
|
||||
callback.invoke(null, resp);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void createUserWithEmail(final String email, final String password, final Callback callback) {
|
||||
mAuth.createUserWithEmailAndPassword(email, password)
|
||||
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<AuthResult> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
userCallback(task.getResult().getUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void signInWithEmail(final String email, final String password, final Callback callback) {
|
||||
|
||||
mAuth.signInWithEmailAndPassword(email, password)
|
||||
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<AuthResult> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
userCallback(task.getResult().getUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void signInWithProvider(final String provider, final String authToken, final String authSecret, final Callback callback) {
|
||||
if (provider.equals("facebook")) {
|
||||
this.facebookLogin(authToken, callback);
|
||||
} else if (provider.equals("google")) {
|
||||
this.googleLogin(authToken, callback);
|
||||
} else
|
||||
// TODO
|
||||
Utils.todoNote(TAG, "signInWithProvider", callback);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void linkPassword(final String email, final String password, final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user != null) {
|
||||
AuthCredential credential = EmailAuthProvider.getCredential(email, password);
|
||||
user
|
||||
.linkWithCredential(credential)
|
||||
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<AuthResult> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "user linked with password credential");
|
||||
userCallback(mAuth.getCurrentUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbackNoUser(callback, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void link(final String provider, final String authToken, final String authSecret, final Callback callback) {
|
||||
if (provider.equals("password")) {
|
||||
linkPassword(authToken, authSecret, callback);
|
||||
} else
|
||||
// TODO other providers
|
||||
Utils.todoNote(TAG, "linkWithProvider", callback);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void signInAnonymously(final Callback callback) {
|
||||
Log.d(TAG, "signInAnonymously:called:");
|
||||
mAuth.signInAnonymously()
|
||||
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<AuthResult> task) {
|
||||
Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful());
|
||||
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
userCallback(task.getResult().getUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void signInWithCustomToken(final String customToken, final Callback callback) {
|
||||
mAuth.signInWithCustomToken(customToken)
|
||||
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<AuthResult> task) {
|
||||
Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful());
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
userCallback(task.getResult().getUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void reauthenticateWithCredentialForProvider(final String provider, final String authToken, final String authSecret, final Callback callback) {
|
||||
// TODO:
|
||||
Utils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback);
|
||||
// AuthCredential credential;
|
||||
// Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void updateUserEmail(final String email, final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user != null) {
|
||||
user
|
||||
.updateEmail(email)
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "User email address updated");
|
||||
userCallback(mAuth.getCurrentUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbackNoUser(callback, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void updateUserPassword(final String newPassword, final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user != null) {
|
||||
user.updatePassword(newPassword)
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "User password updated");
|
||||
userCallback(mAuth.getCurrentUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbackNoUser(callback, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void sendPasswordResetWithEmail(final String email, final Callback callback) {
|
||||
mAuth.sendPasswordResetEmail(email)
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("status", "complete");
|
||||
callback.invoke(null, resp);
|
||||
} else {
|
||||
callback.invoke(task.getException().toString());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void deleteUser(final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
if (user != null) {
|
||||
user.delete()
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "User account deleted");
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("status", "complete");
|
||||
resp.putString("msg", "User account deleted");
|
||||
callback.invoke(null, resp);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbackNoUser(callback, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ReactMethod
|
||||
public void sendEmailVerification(final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user != null) {
|
||||
user.sendEmailVerification()
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("status", "complete");
|
||||
resp.putString("msg", "User verification email sent");
|
||||
callback.invoke(null, resp);
|
||||
} else {
|
||||
WritableMap err = Arguments.createMap();
|
||||
err.putInt("errorCode", ERROR_SENDING_VERIFICATION_EMAIL);
|
||||
err.putString("errorMessage", task.getException().getMessage());
|
||||
callback.invoke(err);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbackNoUser(callback, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ReactMethod
|
||||
public void getToken(final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user != null) {
|
||||
user.getToken(true)
|
||||
.addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<GetTokenResult> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
String token = task.getResult().getToken();
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("status", "complete");
|
||||
resp.putString("token", token);
|
||||
callback.invoke(null, resp);
|
||||
} else {
|
||||
WritableMap err = Arguments.createMap();
|
||||
err.putInt("errorCode", ERROR_FETCHING_TOKEN);
|
||||
err.putString("errorMessage", task.getException().getMessage());
|
||||
callback.invoke(err);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbackNoUser(callback, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void updateUserProfile(ReadableMap props, final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user != null) {
|
||||
UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder();
|
||||
|
||||
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
||||
|
||||
if (m.containsKey("displayName")) {
|
||||
String displayName = (String) m.get("displayName");
|
||||
profileBuilder.setDisplayName(displayName);
|
||||
}
|
||||
|
||||
if (m.containsKey("photoUri")) {
|
||||
String photoUriStr = (String) m.get("photoUri");
|
||||
Uri uri = Uri.parse(photoUriStr);
|
||||
profileBuilder.setPhotoUri(uri);
|
||||
}
|
||||
|
||||
UserProfileChangeRequest profileUpdates = profileBuilder.build();
|
||||
|
||||
user.updateProfile(profileUpdates)
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "User profile updated");
|
||||
userCallback(mAuth.getCurrentUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbackNoUser(callback, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void signOut(final Callback callback) {
|
||||
mAuth.signOut();
|
||||
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("status", "complete");
|
||||
resp.putString("msg", "User signed out");
|
||||
callback.invoke(null, resp);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void reloadUser(final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user == null) {
|
||||
callbackNoUser(callback, false);
|
||||
} else {
|
||||
user.reload()
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "user reloaded");
|
||||
userCallback(mAuth.getCurrentUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getCurrentUser(final Callback callback) {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user == null) {
|
||||
callbackNoUser(callback, false);
|
||||
} else {
|
||||
Log.d("USRC", user.getUid());
|
||||
userCallback(user, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check these things
|
||||
@ReactMethod
|
||||
public void googleLogin(String IdToken, final Callback callback) {
|
||||
AuthCredential credential = GoogleAuthProvider.getCredential(IdToken, null);
|
||||
mAuth.signInWithCredential(credential)
|
||||
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<AuthResult> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
userCallback(task.getResult().getUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void facebookLogin(String Token, final Callback callback) {
|
||||
AuthCredential credential = FacebookAuthProvider.getCredential(Token);
|
||||
mAuth.signInWithCredential(credential)
|
||||
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<AuthResult> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
userCallback(task.getResult().getUser(), callback);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Internal helpers
|
||||
private void userCallback(final FirebaseUser user, final Callback callback) {
|
||||
if (user != null) {
|
||||
user.getToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<GetTokenResult> task) {
|
||||
try {
|
||||
if (task.isSuccessful()) {
|
||||
WritableMap userMap = getUserMap(user);
|
||||
userMap.putString("token", task.getResult().getToken());
|
||||
callback.invoke(null, userMap);
|
||||
} else {
|
||||
userErrorCallback(task, callback);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
userExceptionCallback(ex, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbackNoUser(callback, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void userErrorCallback(Task task, final Callback onFail) {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString("code", ((FirebaseAuthException) task.getException()).getErrorCode());
|
||||
error.putString("message", task.getException().getMessage());
|
||||
onFail.invoke(error);
|
||||
}
|
||||
|
||||
private void userExceptionCallback(Exception ex, final Callback onFail) {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putInt("code", ex.hashCode());
|
||||
error.putString("message", ex.getMessage());
|
||||
onFail.invoke(error);
|
||||
}
|
||||
|
||||
private WritableMap getUserMap(FirebaseUser user) {
|
||||
WritableMap userMap = Arguments.createMap();
|
||||
if (user != null) {
|
||||
final String email = user.getEmail();
|
||||
final String uid = user.getUid();
|
||||
final String provider = user.getProviderId();
|
||||
final String name = user.getDisplayName();
|
||||
final Boolean verified = user.isEmailVerified();
|
||||
final Uri photoUrl = user.getPhotoUrl();
|
||||
|
||||
userMap.putString("email", email);
|
||||
userMap.putString("uid", uid);
|
||||
userMap.putString("providerId", provider);
|
||||
userMap.putBoolean("emailVerified", verified);
|
||||
|
||||
if (name != null) {
|
||||
userMap.putString("name", name);
|
||||
}
|
||||
|
||||
if (photoUrl != null) {
|
||||
userMap.putString("photoURL", photoUrl.toString());
|
||||
}
|
||||
} else {
|
||||
userMap.putString("msg", "no user");
|
||||
}
|
||||
|
||||
return userMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package io.invertase.firebase.database;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
|
||||
import com.google.firebase.database.OnDisconnect;
|
||||
import com.google.firebase.database.DatabaseError;
|
||||
import com.google.firebase.database.DatabaseReference;
|
||||
import com.google.firebase.database.FirebaseDatabase;
|
||||
import com.google.firebase.database.ServerValue;
|
||||
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebaseDatabase";
|
||||
private HashMap<String, RNFirebaseDatabaseReference> mDBListeners = new HashMap<String, RNFirebaseDatabaseReference>();
|
||||
private FirebaseDatabase mFirebaseDatabase;
|
||||
|
||||
public RNFirebaseDatabase(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mFirebaseDatabase = FirebaseDatabase.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
// Persistence
|
||||
@ReactMethod
|
||||
public void enablePersistence(
|
||||
final Boolean enable,
|
||||
final Callback callback) {
|
||||
try {
|
||||
mFirebaseDatabase.setPersistenceEnabled(enable);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t);
|
||||
}
|
||||
|
||||
WritableMap res = Arguments.createMap();
|
||||
res.putString("status", "success");
|
||||
callback.invoke(null, res);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void keepSynced(
|
||||
final String path,
|
||||
final Boolean enable,
|
||||
final Callback callback) {
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
ref.keepSynced(enable);
|
||||
|
||||
WritableMap res = Arguments.createMap();
|
||||
res.putString("status", "success");
|
||||
res.putString("path", path);
|
||||
callback.invoke(null, res);
|
||||
}
|
||||
|
||||
// RNFirebaseDatabase
|
||||
@ReactMethod
|
||||
public void set(
|
||||
final String path,
|
||||
final ReadableMap props,
|
||||
final Callback callback) {
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
||||
|
||||
|
||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||
handleCallback("set", callback, error);
|
||||
}
|
||||
};
|
||||
|
||||
ref.setValue(m.get("value"), listener);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void update(final String path,
|
||||
final ReadableMap props,
|
||||
final Callback callback) {
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
||||
|
||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||
handleCallback("update", callback, error);
|
||||
}
|
||||
};
|
||||
|
||||
ref.updateChildren(m, listener);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void remove(final String path,
|
||||
final Callback callback) {
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||
handleCallback("remove", callback, error);
|
||||
}
|
||||
};
|
||||
|
||||
ref.removeValue(listener);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void push(final String path,
|
||||
final ReadableMap props,
|
||||
final Callback callback) {
|
||||
|
||||
Log.d(TAG, "Called push with " + path);
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
DatabaseReference newRef = ref.push();
|
||||
|
||||
final Uri url = Uri.parse(newRef.toString());
|
||||
final String newPath = url.getPath();
|
||||
|
||||
ReadableMapKeySetIterator iterator = props.keySetIterator();
|
||||
if (iterator.hasNextKey()) {
|
||||
Log.d(TAG, "Passed value to push");
|
||||
// lame way to check if the `props` are empty
|
||||
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
||||
|
||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||
if (error != null) {
|
||||
WritableMap err = Arguments.createMap();
|
||||
err.putInt("errorCode", error.getCode());
|
||||
err.putString("errorDetails", error.getDetails());
|
||||
err.putString("description", error.getMessage());
|
||||
callback.invoke(err);
|
||||
} else {
|
||||
WritableMap res = Arguments.createMap();
|
||||
res.putString("status", "success");
|
||||
res.putString("ref", newPath);
|
||||
callback.invoke(null, res);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
newRef.setValue(m.get("value"), listener);
|
||||
} else {
|
||||
Log.d(TAG, "No value passed to push: " + newPath);
|
||||
WritableMap res = Arguments.createMap();
|
||||
res.putString("status", "success");
|
||||
res.putString("ref", newPath);
|
||||
callback.invoke(null, res);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void on(final String path,
|
||||
final String modifiersString,
|
||||
final ReadableArray modifiersArray,
|
||||
final String eventName,
|
||||
final Callback callback) {
|
||||
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
|
||||
|
||||
if (eventName.equals("value")) {
|
||||
ref.addValueEventListener();
|
||||
} else {
|
||||
ref.addChildEventListener(eventName);
|
||||
}
|
||||
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("status", "success");
|
||||
resp.putString("handle", path);
|
||||
callback.invoke(null, resp);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void onOnce(final String path,
|
||||
final String modifiersString,
|
||||
final ReadableArray modifiersArray,
|
||||
final String eventName,
|
||||
final Callback callback) {
|
||||
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
|
||||
ref.addOnceValueEventListener(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* At the time of this writing, off() only gets called when there are no more subscribers to a given path.
|
||||
* `mListeners` might therefore be out of sync (though javascript isnt listening for those eventTypes, so
|
||||
* it doesn't really matter- just polluting the RN bridge a little more than necessary.
|
||||
* off() should therefore clean *everything* up
|
||||
*/
|
||||
@ReactMethod
|
||||
public void off(
|
||||
final String path,
|
||||
final String modifiersString,
|
||||
final String eventName,
|
||||
final Callback callback) {
|
||||
|
||||
String key = this.getDBListenerKey(path, modifiersString);
|
||||
RNFirebaseDatabaseReference r = mDBListeners.get(key);
|
||||
|
||||
if (r != null) {
|
||||
if (eventName == null || "".equals(eventName)) {
|
||||
r.cleanup();
|
||||
mDBListeners.remove(key);
|
||||
} else {
|
||||
r.removeEventListener(eventName);
|
||||
if (!r.hasListeners()) {
|
||||
mDBListeners.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Removed listener " + path + "/" + modifiersString);
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putString("handle", path);
|
||||
resp.putString("status", "success");
|
||||
resp.putString("modifiersString", modifiersString);
|
||||
//TODO: Remaining listeners
|
||||
callback.invoke(null, resp);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void onDisconnectSet(final String path, final ReadableMap props, final Callback callback) {
|
||||
String type = props.getString("type");
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
OnDisconnect od = ref.onDisconnect();
|
||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||
handleCallback("onDisconnectSet", callback, error);
|
||||
}
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case "object":
|
||||
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props.getMap("value"));
|
||||
od.setValue(map, listener);
|
||||
break;
|
||||
case "array":
|
||||
List<Object> list = Utils.recursivelyDeconstructReadableArray(props.getArray("value"));
|
||||
od.setValue(list, listener);
|
||||
break;
|
||||
case "string":
|
||||
od.setValue(props.getString("value"), listener);
|
||||
break;
|
||||
case "number":
|
||||
od.setValue(props.getDouble("value"), listener);
|
||||
break;
|
||||
case "boolean":
|
||||
od.setValue(props.getBoolean("value"), listener);
|
||||
break;
|
||||
case "null":
|
||||
od.setValue(null, listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void onDisconnectUpdate(final String path, final ReadableMap props, final Callback callback) {
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
OnDisconnect od = ref.onDisconnect();
|
||||
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props);
|
||||
od.updateChildren(map, new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||
handleCallback("onDisconnectUpdate", callback, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void onDisconnectRemove(final String path, final Callback callback) {
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
|
||||
OnDisconnect od = ref.onDisconnect();
|
||||
od.removeValue(new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||
handleCallback("onDisconnectRemove", callback, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void onDisconnectCancel(final String path, final Callback callback) {
|
||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||
|
||||
OnDisconnect od = ref.onDisconnect();
|
||||
od.cancel(new DatabaseReference.CompletionListener() {
|
||||
@Override
|
||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||
handleCallback("onDisconnectCancel", callback, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void goOnline() {
|
||||
mFirebaseDatabase.goOnline();
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void goOffline() {
|
||||
mFirebaseDatabase.goOffline();
|
||||
}
|
||||
|
||||
private void handleCallback(
|
||||
final String methodName,
|
||||
final Callback callback,
|
||||
final DatabaseError databaseError) {
|
||||
if (databaseError != null) {
|
||||
WritableMap err = Arguments.createMap();
|
||||
err.putInt("errorCode", databaseError.getCode());
|
||||
err.putString("errorDetails", databaseError.getDetails());
|
||||
err.putString("description", databaseError.getMessage());
|
||||
callback.invoke(err);
|
||||
} else {
|
||||
WritableMap res = Arguments.createMap();
|
||||
res.putString("status", "success");
|
||||
res.putString("method", methodName);
|
||||
callback.invoke(null, res);
|
||||
}
|
||||
}
|
||||
|
||||
private RNFirebaseDatabaseReference getDBHandle(final String path,
|
||||
final ReadableArray modifiersArray,
|
||||
final String modifiersString) {
|
||||
String key = this.getDBListenerKey(path, modifiersString);
|
||||
RNFirebaseDatabaseReference r = mDBListeners.get(key);
|
||||
|
||||
if (r == null) {
|
||||
ReactContext ctx = getReactApplicationContext();
|
||||
r = new RNFirebaseDatabaseReference(ctx, mFirebaseDatabase, path, modifiersArray, modifiersString);
|
||||
mDBListeners.put(key, r);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
private String getDBListenerKey(String path, String modifiersString) {
|
||||
return path + " | " + modifiersString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
final Map<String, Object> constants = new HashMap<>();
|
||||
constants.put("serverValueTimestamp", ServerValue.TIMESTAMP);
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package io.invertase.firebase.database;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import android.util.Log;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Set;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
|
||||
import com.google.firebase.database.Query;
|
||||
import com.google.firebase.database.DataSnapshot;
|
||||
import com.google.firebase.database.DatabaseError;
|
||||
import com.google.firebase.database.FirebaseDatabase;
|
||||
import com.google.firebase.database.ChildEventListener;
|
||||
import com.google.firebase.database.ValueEventListener;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
public class RNFirebaseDatabaseReference {
|
||||
private static final String TAG = "RNFirebaseDBReference";
|
||||
|
||||
private Query mQuery;
|
||||
private String mPath;
|
||||
private String mModifiersString;
|
||||
private ChildEventListener mEventListener;
|
||||
private ValueEventListener mValueListener;
|
||||
private ReactContext mReactContext;
|
||||
private Set<String> childEventListeners = new HashSet<>();
|
||||
|
||||
public RNFirebaseDatabaseReference(final ReactContext context,
|
||||
final FirebaseDatabase firebaseDatabase,
|
||||
final String path,
|
||||
final ReadableArray modifiersArray,
|
||||
final String modifiersString) {
|
||||
mReactContext = context;
|
||||
mPath = path;
|
||||
mModifiersString = modifiersString;
|
||||
mQuery = this.buildDatabaseQueryAtPathAndModifiers(firebaseDatabase, path, modifiersArray);
|
||||
}
|
||||
|
||||
public void addChildEventListener(final String eventName) {
|
||||
if (mEventListener == null) {
|
||||
mEventListener = new ChildEventListener() {
|
||||
@Override
|
||||
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
handleDatabaseEvent("child_added", dataSnapshot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
handleDatabaseEvent("child_changed", dataSnapshot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildRemoved(DataSnapshot dataSnapshot) {
|
||||
handleDatabaseEvent("child_removed", dataSnapshot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
handleDatabaseEvent("child_moved", dataSnapshot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
handleDatabaseError(error);
|
||||
}
|
||||
};
|
||||
mQuery.addChildEventListener(mEventListener);
|
||||
Log.d(TAG, "Added ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
|
||||
} else {
|
||||
Log.w(TAG, "Trying to add duplicate ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
|
||||
}
|
||||
//Keep track of the events that the JS is interested in knowing about
|
||||
childEventListeners.add(eventName);
|
||||
}
|
||||
|
||||
public void addValueEventListener() {
|
||||
if (mValueListener == null) {
|
||||
mValueListener = new ValueEventListener() {
|
||||
@Override
|
||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||
handleDatabaseEvent("value", dataSnapshot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
handleDatabaseError(error);
|
||||
}
|
||||
};
|
||||
mQuery.addValueEventListener(mValueListener);
|
||||
Log.d(TAG, "Added ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
|
||||
//this.setListeningTo(mPath, modifiersString, "value");
|
||||
} else {
|
||||
Log.w(TAG, "Trying to add duplicate ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
|
||||
}
|
||||
}
|
||||
|
||||
public void addOnceValueEventListener(final Callback callback) {
|
||||
final ValueEventListener onceValueEventListener = new ValueEventListener() {
|
||||
@Override
|
||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||
WritableMap data = Utils.dataSnapshotToMap("value", mPath, mModifiersString, dataSnapshot);
|
||||
callback.invoke(null, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
WritableMap err = Arguments.createMap();
|
||||
err.putInt("errorCode", error.getCode());
|
||||
err.putString("errorDetails", error.getDetails());
|
||||
err.putString("description", error.getMessage());
|
||||
callback.invoke(err);
|
||||
}
|
||||
};
|
||||
mQuery.addListenerForSingleValueEvent(onceValueEventListener);
|
||||
Log.d(TAG, "Added OnceValueEventListener for path: " + mPath + " with modifiers " + mModifiersString);
|
||||
}
|
||||
|
||||
public void removeEventListener(String eventName) {
|
||||
if ("value".equals(eventName)) {
|
||||
this.removeValueEventListener();
|
||||
} else {
|
||||
childEventListeners.remove(eventName);
|
||||
if (childEventListeners.isEmpty()) {
|
||||
this.removeChildEventListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasListeners() {
|
||||
return mEventListener != null || mValueListener != null;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
Log.d(TAG, "cleaning up database reference " + this);
|
||||
childEventListeners.clear();
|
||||
this.removeChildEventListener();
|
||||
this.removeValueEventListener();
|
||||
}
|
||||
|
||||
private void removeChildEventListener() {
|
||||
if (mEventListener != null) {
|
||||
mQuery.removeEventListener(mEventListener);
|
||||
mEventListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeValueEventListener() {
|
||||
if (mValueListener != null) {
|
||||
mQuery.removeEventListener(mValueListener);
|
||||
mValueListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) {
|
||||
WritableMap data = Utils.dataSnapshotToMap(name, mPath, mModifiersString, dataSnapshot);
|
||||
WritableMap evt = Arguments.createMap();
|
||||
evt.putString("eventName", name);
|
||||
evt.putMap("body", data);
|
||||
|
||||
Utils.sendEvent(mReactContext, "database_event", evt);
|
||||
}
|
||||
|
||||
private void handleDatabaseError(final DatabaseError error) {
|
||||
WritableMap err = Arguments.createMap();
|
||||
err.putString("eventName", "database_error");
|
||||
err.putString("path", mPath);
|
||||
err.putString("modifiersString", mModifiersString);
|
||||
err.putInt("errorCode", error.getCode());
|
||||
err.putString("errorDetails", error.getDetails());
|
||||
err.putString("msg", error.getMessage());
|
||||
|
||||
WritableMap evt = Arguments.createMap();
|
||||
evt.putString("eventName", "database_error");
|
||||
evt.putMap("body", err);
|
||||
|
||||
Utils.sendEvent(mReactContext, "database_error", evt);
|
||||
}
|
||||
|
||||
private Query buildDatabaseQueryAtPathAndModifiers(final FirebaseDatabase firebaseDatabase,
|
||||
final String path,
|
||||
final ReadableArray modifiers) {
|
||||
Query query = firebaseDatabase.getReference(path);
|
||||
List<Object> strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers);
|
||||
ListIterator<Object> it = strModifiers.listIterator();
|
||||
|
||||
while(it.hasNext()) {
|
||||
String str = (String) it.next();
|
||||
|
||||
String[] strArr = str.split(":");
|
||||
String methStr = strArr[0];
|
||||
|
||||
if (methStr.equalsIgnoreCase("orderByKey")) {
|
||||
query = query.orderByKey();
|
||||
} else if (methStr.equalsIgnoreCase("orderByValue")) {
|
||||
query = query.orderByValue();
|
||||
} else if (methStr.equalsIgnoreCase("orderByPriority")) {
|
||||
query = query.orderByPriority();
|
||||
} else if (methStr.contains("orderByChild")) {
|
||||
String key = strArr[1];
|
||||
Log.d(TAG, "orderByChild: " + key);
|
||||
query = query.orderByChild(key);
|
||||
} else if (methStr.contains("limitToLast")) {
|
||||
String key = strArr[1];
|
||||
int limit = Integer.parseInt(key);
|
||||
Log.d(TAG, "limitToLast: " + limit);
|
||||
query = query.limitToLast(limit);
|
||||
} else if (methStr.contains("limitToFirst")) {
|
||||
String key = strArr[1];
|
||||
int limit = Integer.parseInt(key);
|
||||
Log.d(TAG, "limitToFirst: " + limit);
|
||||
query = query.limitToFirst(limit);
|
||||
} else if (methStr.contains("equalTo")) {
|
||||
String value = strArr[1];
|
||||
String type = strArr[2];
|
||||
if ("number".equals(type)) {
|
||||
double doubleValue = Double.parseDouble(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.equalTo(doubleValue, strArr[3]);
|
||||
} else {
|
||||
query = query.equalTo(doubleValue);
|
||||
}
|
||||
} else if ("boolean".equals(type)) {
|
||||
boolean booleanValue = Boolean.parseBoolean(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.equalTo(booleanValue, strArr[3] );
|
||||
} else {
|
||||
query = query.equalTo(booleanValue);
|
||||
}
|
||||
} else {
|
||||
if (strArr.length > 3) {
|
||||
query = query.equalTo(value, strArr[3]);
|
||||
} else {
|
||||
query = query.equalTo(value);
|
||||
}
|
||||
}
|
||||
} else if (methStr.contains("endAt")) {
|
||||
String value = strArr[1];
|
||||
String type = strArr[2];
|
||||
if ("number".equals(type)) {
|
||||
double doubleValue = Double.parseDouble(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.endAt(doubleValue, strArr[3]);
|
||||
} else {
|
||||
query = query.endAt(doubleValue);
|
||||
}
|
||||
} else if ("boolean".equals(type)) {
|
||||
boolean booleanValue = Boolean.parseBoolean(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.endAt(booleanValue, strArr[3] );
|
||||
} else {
|
||||
query = query.endAt(booleanValue);
|
||||
}
|
||||
} else {
|
||||
if (strArr.length > 3) {
|
||||
query = query.endAt(value, strArr[3]);
|
||||
} else {
|
||||
query = query.endAt(value);
|
||||
}
|
||||
}
|
||||
} else if (methStr.contains("startAt")) {
|
||||
String value = strArr[1];
|
||||
String type = strArr[2];
|
||||
if ("number".equals(type)) {
|
||||
double doubleValue = Double.parseDouble(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.startAt(doubleValue, strArr[3]);
|
||||
} else {
|
||||
query = query.startAt(doubleValue);
|
||||
}
|
||||
} else if ("boolean".equals(type)) {
|
||||
boolean booleanValue = Boolean.parseBoolean(value);
|
||||
if (strArr.length > 3) {
|
||||
query = query.startAt(booleanValue, strArr[3] );
|
||||
} else {
|
||||
query = query.startAt(booleanValue);
|
||||
}
|
||||
} else {
|
||||
if (strArr.length > 3) {
|
||||
query = query.startAt(value, strArr[3]);
|
||||
} else {
|
||||
query = query.startAt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package io.invertase.firebase.messaging;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.Intent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.ReadableType;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
import com.google.firebase.messaging.FirebaseMessaging;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
public class RNFirebaseMessaging extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "RNFirebaseMessaging";
|
||||
private static final String EVENT_NAME_TOKEN = "RNFirebaseRefreshToken";
|
||||
private static final String EVENT_NAME_NOTIFICATION = "RNFirebaseReceiveNotification";
|
||||
private static final String EVENT_NAME_SEND = "RNFirebaseUpstreamSend";
|
||||
|
||||
public static final String INTENT_NAME_TOKEN = "io.invertase.firebase.refreshToken";
|
||||
public static final String INTENT_NAME_NOTIFICATION = "io.invertase.firebase.ReceiveNotification";
|
||||
public static final String INTENT_NAME_SEND = "io.invertase.firebase.Upstream";
|
||||
|
||||
private IntentFilter mRefreshTokenIntentFilter;
|
||||
private IntentFilter mReceiveNotificationIntentFilter;
|
||||
private IntentFilter mReceiveSendIntentFilter;
|
||||
private BroadcastReceiver mBroadcastReceiver;
|
||||
|
||||
public RNFirebaseMessaging(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN);
|
||||
mReceiveNotificationIntentFilter = new IntentFilter(INTENT_NAME_NOTIFICATION);
|
||||
mReceiveSendIntentFilter = new IntentFilter(INTENT_NAME_SEND);
|
||||
initRefreshTokenHandler();
|
||||
initMessageHandler();
|
||||
initSendHandler();
|
||||
Log.d(TAG, "New instance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void initMessageHandler() {
|
||||
Log.d(TAG, "RNFirebase initMessageHandler called");
|
||||
|
||||
if (mBroadcastReceiver == null) {
|
||||
mBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
RemoteMessage remoteMessage = intent.getParcelableExtra("data");
|
||||
Log.d(TAG, "Firebase onReceive: " + remoteMessage);
|
||||
WritableMap params = Arguments.createMap();
|
||||
|
||||
params.putNull("data");
|
||||
params.putNull("notification");
|
||||
params.putString("id", remoteMessage.getMessageId());
|
||||
params.putString("messageId", remoteMessage.getMessageId());
|
||||
|
||||
|
||||
if (remoteMessage.getData().size() != 0) {
|
||||
WritableMap dataMap = Arguments.createMap();
|
||||
Map<String, String> data = remoteMessage.getData();
|
||||
|
||||
for (String key : data.keySet()) {
|
||||
dataMap.putString(key, data.get(key));
|
||||
}
|
||||
|
||||
params.putMap("data", dataMap);
|
||||
}
|
||||
|
||||
|
||||
if (remoteMessage.getNotification() != null) {
|
||||
WritableMap notificationMap = Arguments.createMap();
|
||||
RemoteMessage.Notification notification = remoteMessage.getNotification();
|
||||
notificationMap.putString("title", notification.getTitle());
|
||||
notificationMap.putString("body", notification.getBody());
|
||||
notificationMap.putString("icon", notification.getIcon());
|
||||
notificationMap.putString("sound", notification.getSound());
|
||||
notificationMap.putString("tag", notification.getTag());
|
||||
params.putMap("notification", notificationMap);
|
||||
}
|
||||
|
||||
ReactContext ctx = getReactApplicationContext();
|
||||
Utils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
getReactApplicationContext().registerReceiver(mBroadcastReceiver, mReceiveNotificationIntentFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void initRefreshTokenHandler() {
|
||||
getReactApplicationContext().registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("token", intent.getStringExtra("token"));
|
||||
ReactContext ctx = getReactApplicationContext();
|
||||
Log.d(TAG, "initRefreshTokenHandler received event " + EVENT_NAME_TOKEN);
|
||||
Utils.sendEvent(ctx, EVENT_NAME_TOKEN, params);
|
||||
}
|
||||
|
||||
;
|
||||
}, mRefreshTokenIntentFilter);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void subscribeToTopic(String topic, final Callback callback) {
|
||||
try {
|
||||
FirebaseMessaging.getInstance().subscribeToTopic(topic);
|
||||
callback.invoke(null, topic);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.d(TAG, "Firebase token: " + e);
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString("message", e.getMessage());
|
||||
callback.invoke(error);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getToken(final Callback callback) {
|
||||
|
||||
try {
|
||||
String token = FirebaseInstanceId.getInstance().getToken();
|
||||
Log.d(TAG, "Firebase token: " + token);
|
||||
callback.invoke(null, token);
|
||||
} catch (Exception e) {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString("message", e.getMessage());
|
||||
callback.invoke(error);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void unsubscribeFromTopic(String topic, final Callback callback) {
|
||||
try {
|
||||
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
|
||||
callback.invoke(null, topic);
|
||||
} catch (Exception e) {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString("message", e.getMessage());
|
||||
callback.invoke(error);
|
||||
}
|
||||
}
|
||||
|
||||
// String senderId, String messageId, String messageType,
|
||||
@ReactMethod
|
||||
public void send(ReadableMap params, final Callback callback) {
|
||||
ReadableMap data = params.getMap("data");
|
||||
FirebaseMessaging fm = FirebaseMessaging.getInstance();
|
||||
RemoteMessage.Builder remoteMessage = new RemoteMessage.Builder(params.getString("sender"));
|
||||
|
||||
remoteMessage.setMessageId(params.getString("id"));
|
||||
remoteMessage.setMessageType(params.getString("type"));
|
||||
|
||||
if (params.hasKey("ttl")) {
|
||||
remoteMessage.setTtl(params.getInt("ttl"));
|
||||
}
|
||||
|
||||
if (params.hasKey("collapseKey")) {
|
||||
remoteMessage.setCollapseKey(params.getString("collapseKey"));
|
||||
}
|
||||
|
||||
ReadableMapKeySetIterator iterator = data.keySetIterator();
|
||||
|
||||
while (iterator.hasNextKey()) {
|
||||
String key = iterator.nextKey();
|
||||
ReadableType type = data.getType(key);
|
||||
if (type == ReadableType.String) {
|
||||
remoteMessage.addData(key, data.getString(key));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fm.send(remoteMessage.build());
|
||||
WritableMap res = Arguments.createMap();
|
||||
res.putString("status", "success");
|
||||
Log.d(TAG, "send: Message sent");
|
||||
callback.invoke(null, res);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "send: error sending message", e);
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString("code", e.toString());
|
||||
error.putString("message", e.toString());
|
||||
callback.invoke(error);
|
||||
}
|
||||
}
|
||||
|
||||
private void initSendHandler() {
|
||||
getReactApplicationContext().registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
WritableMap params = Arguments.createMap();
|
||||
if (intent.getBooleanExtra("hasError", false)) {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putInt("code", intent.getIntExtra("errCode", 0));
|
||||
error.putString("message", intent.getStringExtra("errorMessage"));
|
||||
params.putMap("err", error);
|
||||
} else {
|
||||
params.putNull("err");
|
||||
}
|
||||
ReactContext ctx = getReactApplicationContext();
|
||||
Utils.sendEvent(ctx, EVENT_NAME_SEND, params);
|
||||
}
|
||||
}, mReceiveSendIntentFilter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
package io.invertase.firebase.storage;
|
||||
|
||||
import android.util.Log;
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.database.Cursor;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
|
||||
import com.google.firebase.database.DataSnapshot;
|
||||
import com.google.firebase.database.DatabaseError;
|
||||
import com.google.firebase.storage.StorageException;
|
||||
import com.google.firebase.storage.StorageTask;
|
||||
import com.google.firebase.storage.StreamDownloadTask;
|
||||
import com.google.firebase.storage.UploadTask;
|
||||
import com.google.firebase.storage.FirebaseStorage;
|
||||
import com.google.firebase.storage.StorageMetadata;
|
||||
import com.google.firebase.storage.StorageReference;
|
||||
import com.google.firebase.storage.OnPausedListener;
|
||||
import com.google.firebase.storage.OnProgressListener;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class RNFirebaseStorage extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "RNFirebaseStorage";
|
||||
private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH";
|
||||
private static final String ExternalDirectoryPath = "EXTERNAL_DIRECTORY_PATH";
|
||||
private static final String ExternalStorageDirectoryPath = "EXTERNAL_STORAGE_DIRECTORY_PATH";
|
||||
private static final String PicturesDirectoryPath = "PICTURES_DIRECTORY_PATH";
|
||||
private static final String TemporaryDirectoryPath = "TEMPORARY_DIRECTORY_PATH";
|
||||
private static final String CachesDirectoryPath = "CACHES_DIRECTORY_PATH";
|
||||
private static final String DocumentDirectory = "DOCUMENT_DIRECTORY_PATH";
|
||||
|
||||
private static final String FileTypeRegular = "FILETYPE_REGULAR";
|
||||
private static final String FileTypeDirectory = "FILETYPE_DIRECTORY";
|
||||
|
||||
private static final String STORAGE_EVENT = "storage_event";
|
||||
private static final String STORAGE_ERROR = "storage_error";
|
||||
private static final String STORAGE_STATE_CHANGED = "state_changed";
|
||||
private static final String STORAGE_UPLOAD_SUCCESS = "upload_success";
|
||||
private static final String STORAGE_UPLOAD_FAILURE = "upload_failure";
|
||||
private static final String STORAGE_DOWNLOAD_SUCCESS = "download_success";
|
||||
private static final String STORAGE_DOWNLOAD_FAILURE = "download_failure";
|
||||
|
||||
private ReactContext mReactContext;
|
||||
|
||||
public RNFirebaseStorage(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
Log.d(TAG, "New instance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
|
||||
public boolean isExternalStorageWritable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
return Environment.MEDIA_MOUNTED.equals(state);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void delete(final String path,
|
||||
final Callback callback) {
|
||||
StorageReference reference = this.getReference(path);
|
||||
reference.delete().addOnSuccessListener(new OnSuccessListener<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void aVoid) {
|
||||
WritableMap data = Arguments.createMap();
|
||||
data.putString("success", "success");
|
||||
data.putString("path", path);
|
||||
callback.invoke(null, data);
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(Exception exception) {
|
||||
callback.invoke(makeErrorPayload(1, exception));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getDownloadURL(final String path,
|
||||
final Callback callback) {
|
||||
Log.d(TAG, "Download url for remote path: " + path);
|
||||
final StorageReference reference = this.getReference(path);
|
||||
|
||||
Task<Uri> downloadTask = reference.getDownloadUrl();
|
||||
downloadTask
|
||||
.addOnSuccessListener(new OnSuccessListener<Uri>() {
|
||||
@Override
|
||||
public void onSuccess(Uri uri) {
|
||||
callback.invoke(null, uri.toString());
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
callback.invoke(makeErrorPayload(1, exception));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getMetadata(final String path,
|
||||
final Callback callback) {
|
||||
StorageReference reference = this.getReference(path);
|
||||
reference.getMetadata().addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
|
||||
@Override
|
||||
public void onSuccess(StorageMetadata storageMetadata) {
|
||||
WritableMap data = getMetadataAsMap(storageMetadata);
|
||||
callback.invoke(null, data);
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(Exception exception) {
|
||||
callback.invoke(makeErrorPayload(1, exception));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void updateMetadata(final String path,
|
||||
final ReadableMap metadata,
|
||||
final Callback callback) {
|
||||
StorageReference reference = this.getReference(path);
|
||||
StorageMetadata md = buildMetadataFromMap(metadata);
|
||||
reference.updateMetadata(md).addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
|
||||
@Override
|
||||
public void onSuccess(StorageMetadata storageMetadata) {
|
||||
WritableMap data = getMetadataAsMap(storageMetadata);
|
||||
callback.invoke(null, data);
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(Exception exception) {
|
||||
callback.invoke(makeErrorPayload(1, exception));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void downloadFile(final String path,
|
||||
final String localPath,
|
||||
final Callback callback) {
|
||||
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;
|
||||
}
|
||||
Log.d(TAG, "downloadFile from remote path: " + path);
|
||||
|
||||
StorageReference reference = this.getReference(path);
|
||||
|
||||
reference.getStream(new StreamDownloadTask.StreamProcessor() {
|
||||
@Override
|
||||
public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot, InputStream inputStream) throws IOException {
|
||||
int indexOfLastSlash = localPath.lastIndexOf("/");
|
||||
String pathMinusFileName = indexOfLastSlash>0 ? localPath.substring(0, indexOfLastSlash) + "/" : "/";
|
||||
String filename = indexOfLastSlash>0 ? localPath.substring(indexOfLastSlash+1) : localPath;
|
||||
File fileWithJustPath = new File(pathMinusFileName);
|
||||
fileWithJustPath.mkdirs();
|
||||
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<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onProgress(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "Got download progress " + taskSnapshot);
|
||||
WritableMap event = getDownloadTaskAsMap(taskSnapshot);
|
||||
handleStorageEvent(STORAGE_STATE_CHANGED, path, event);
|
||||
}
|
||||
}).addOnPausedListener(new OnPausedListener<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onPaused(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "Download is paused " + taskSnapshot);
|
||||
WritableMap event = getDownloadTaskAsMap(taskSnapshot);
|
||||
handleStorageEvent(STORAGE_STATE_CHANGED, path, event);
|
||||
}
|
||||
}).addOnSuccessListener(new OnSuccessListener<StreamDownloadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "Successfully downloaded file " + taskSnapshot);
|
||||
WritableMap resp = getDownloadTaskAsMap(taskSnapshot);
|
||||
handleStorageEvent(STORAGE_DOWNLOAD_SUCCESS, path, resp);
|
||||
//TODO: A little hacky, but otherwise throws a not consumed exception
|
||||
resp = getDownloadTaskAsMap(taskSnapshot);
|
||||
callback.invoke(null, resp);
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
Log.e(TAG, "Failed to download file " + exception.getMessage());
|
||||
//TODO: JS Error event
|
||||
callback.invoke(makeErrorPayload(1, exception));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void putFile(final String path, final String localPath, final ReadableMap metadata, final Callback callback) {
|
||||
StorageReference reference = this.getReference(path);
|
||||
|
||||
Log.i(TAG, "Upload file: " + localPath + " to " + path);
|
||||
|
||||
try {
|
||||
Uri file;
|
||||
if (localPath.startsWith("content://")) {
|
||||
String realPath = getRealPathFromURI(localPath);
|
||||
file = Uri.fromFile(new File(realPath));
|
||||
} else {
|
||||
file = Uri.fromFile(new File(localPath));
|
||||
}
|
||||
|
||||
StorageMetadata md = buildMetadataFromMap(metadata);
|
||||
UploadTask uploadTask = reference.putFile(file, md);
|
||||
|
||||
// register observers to listen for when the download is done or if it fails
|
||||
uploadTask
|
||||
.addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception exception) {
|
||||
// handle unsuccessful uploads
|
||||
Log.e(TAG, "Failed to upload file " + exception.getMessage());
|
||||
//TODO: JS Error event
|
||||
callback.invoke(makeErrorPayload(1, exception));
|
||||
}
|
||||
})
|
||||
.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "Successfully uploaded file " + taskSnapshot);
|
||||
WritableMap resp = getUploadTaskAsMap(taskSnapshot);
|
||||
handleStorageEvent(STORAGE_UPLOAD_SUCCESS, path, resp);
|
||||
//TODO: A little hacky, but otherwise throws a not consumed exception
|
||||
resp = getUploadTaskAsMap(taskSnapshot);
|
||||
callback.invoke(null, resp);
|
||||
}
|
||||
})
|
||||
.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "Got upload progress " + taskSnapshot);
|
||||
WritableMap event = getUploadTaskAsMap(taskSnapshot);
|
||||
handleStorageEvent(STORAGE_STATE_CHANGED, path, event);
|
||||
}
|
||||
})
|
||||
.addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
|
||||
@Override
|
||||
public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
|
||||
Log.d(TAG, "Upload is paused " + taskSnapshot);
|
||||
WritableMap event = getUploadTaskAsMap(taskSnapshot);
|
||||
handleStorageEvent(STORAGE_STATE_CHANGED, path, event);
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
final int errorCode = 2;
|
||||
callback.invoke(makeErrorPayload(errorCode, ex));
|
||||
}
|
||||
}
|
||||
|
||||
//Firebase.Storage methods
|
||||
@ReactMethod
|
||||
public void setMaxDownloadRetryTime(final double milliseconds) {
|
||||
FirebaseStorage.getInstance().setMaxDownloadRetryTimeMillis((long)milliseconds);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setMaxOperationRetryTime(final double milliseconds) {
|
||||
FirebaseStorage.getInstance().setMaxOperationRetryTimeMillis((long)milliseconds);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setMaxUploadRetryTime(final double milliseconds) {
|
||||
FirebaseStorage.getInstance().setMaxUploadRetryTimeMillis((long)milliseconds);
|
||||
}
|
||||
|
||||
private StorageReference getReference(String path) {
|
||||
if (path.startsWith("url::")) {
|
||||
String url = path.substring(5);
|
||||
return FirebaseStorage.getInstance().getReferenceFromUrl(url);
|
||||
} else {
|
||||
return FirebaseStorage.getInstance().getReference(path);
|
||||
}
|
||||
}
|
||||
|
||||
private StorageMetadata buildMetadataFromMap(ReadableMap metadata) {
|
||||
StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder();
|
||||
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(metadata);
|
||||
|
||||
for (Map.Entry<String, Object> entry : m.entrySet()) {
|
||||
metadataBuilder.setCustomMetadata(entry.getKey(), entry.getValue().toString());
|
||||
}
|
||||
|
||||
return metadataBuilder.build();
|
||||
}
|
||||
|
||||
private WritableMap getMetadataAsMap(StorageMetadata storageMetadata) {
|
||||
WritableMap metadata = Arguments.createMap();
|
||||
metadata.putString("bucket", storageMetadata.getBucket());
|
||||
metadata.putString("generation", storageMetadata.getGeneration());
|
||||
metadata.putString("metageneration", storageMetadata.getMetadataGeneration());
|
||||
metadata.putString("fullPath", storageMetadata.getPath());
|
||||
metadata.putString("name", storageMetadata.getName());
|
||||
metadata.putDouble("size", storageMetadata.getSizeBytes());
|
||||
metadata.putDouble("timeCreated", storageMetadata.getCreationTimeMillis());
|
||||
metadata.putDouble("updated", storageMetadata.getUpdatedTimeMillis());
|
||||
metadata.putString("md5hash", storageMetadata.getMd5Hash());
|
||||
metadata.putString("cacheControl", storageMetadata.getCacheControl());
|
||||
metadata.putString("contentDisposition", storageMetadata.getContentDisposition());
|
||||
metadata.putString("contentEncoding", storageMetadata.getContentEncoding());
|
||||
metadata.putString("contentLanguage", storageMetadata.getContentLanguage());
|
||||
metadata.putString("contentType", storageMetadata.getContentType());
|
||||
|
||||
WritableArray downloadURLs = Arguments.createArray();
|
||||
for (Uri uri : storageMetadata.getDownloadUrls()) {
|
||||
downloadURLs.pushString(uri.getPath());
|
||||
}
|
||||
metadata.putArray("downloadURLs", downloadURLs);
|
||||
|
||||
WritableMap customMetadata = Arguments.createMap();
|
||||
for (String key : storageMetadata.getCustomMetadataKeys()) {
|
||||
customMetadata.putString(key, storageMetadata.getCustomMetadata(key));
|
||||
}
|
||||
metadata.putMap("customMetadata", customMetadata);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private String getRealPathFromURI(final String uri) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
String[] proj = {MediaStore.Images.Media.DATA};
|
||||
cursor = getReactApplicationContext().getContentResolver().query(Uri.parse(uri), proj, null, null, null);
|
||||
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||
cursor.moveToFirst();
|
||||
return cursor.getString(column_index);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WritableMap getDownloadTaskAsMap(final StreamDownloadTask.TaskSnapshot taskSnapshot) {
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred());
|
||||
resp.putString("ref", taskSnapshot.getStorage().getPath());
|
||||
resp.putString("state", this.getTaskStatus(taskSnapshot.getTask()));
|
||||
resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount());
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
private WritableMap getUploadTaskAsMap(final UploadTask.TaskSnapshot taskSnapshot) {
|
||||
StorageMetadata d = taskSnapshot.getMetadata();
|
||||
|
||||
WritableMap resp = Arguments.createMap();
|
||||
resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred());
|
||||
resp.putString("downloadUrl", taskSnapshot.getDownloadUrl() != null ? taskSnapshot.getDownloadUrl().toString() : null);
|
||||
resp.putString("ref", taskSnapshot.getStorage().getPath());
|
||||
resp.putString("state", this.getTaskStatus(taskSnapshot.getTask()));
|
||||
resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount());
|
||||
|
||||
if (taskSnapshot.getMetadata() != null) {
|
||||
WritableMap metadata = getMetadataAsMap(taskSnapshot.getMetadata());
|
||||
resp.putMap("metadata", metadata);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
private String getTaskStatus(StorageTask<?> task) {
|
||||
if (task.isInProgress()) {
|
||||
return "RUNNING";
|
||||
} else if (task.isPaused()) {
|
||||
return "PAUSED";
|
||||
} else if (task.isSuccessful() || task.isComplete()) {
|
||||
return "SUCCESS";
|
||||
} else if (task.isCanceled()) {
|
||||
return "CANCELLED";
|
||||
} else if (task.getException() != null) {
|
||||
return "ERROR";
|
||||
} else {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStorageEvent(final String name, final String path, WritableMap body) {
|
||||
WritableMap evt = Arguments.createMap();
|
||||
evt.putString("eventName", name);
|
||||
evt.putString("path", path);
|
||||
evt.putMap("body", body);
|
||||
|
||||
Utils.sendEvent(this.getReactApplicationContext(), STORAGE_EVENT, evt);
|
||||
}
|
||||
|
||||
private void handleStorageError(final String path, final StorageException error) {
|
||||
WritableMap body = Arguments.createMap();
|
||||
body.putString("path", path);
|
||||
body.putString("message", error.getMessage());
|
||||
|
||||
WritableMap evt = Arguments.createMap();
|
||||
evt.putString("eventName", STORAGE_ERROR);
|
||||
evt.putMap("body", body);
|
||||
|
||||
Utils.sendEvent(this.getReactApplicationContext(), STORAGE_ERROR, evt);
|
||||
}
|
||||
|
||||
private WritableMap makeErrorPayload(double code, Exception ex) {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putDouble("code", code);
|
||||
error.putString("message", ex.getMessage());
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
final Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put(DocumentDirectory, 0);
|
||||
constants.put(DocumentDirectoryPath, this.getReactApplicationContext().getFilesDir().getAbsolutePath());
|
||||
constants.put(TemporaryDirectoryPath, null);
|
||||
constants.put(PicturesDirectoryPath, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath());
|
||||
constants.put(CachesDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath());
|
||||
constants.put(FileTypeRegular, 0);
|
||||
constants.put(FileTypeDirectory, 1);
|
||||
|
||||
File externalStorageDirectory = Environment.getExternalStorageDirectory();
|
||||
if (externalStorageDirectory != null) {
|
||||
constants.put(ExternalStorageDirectoryPath, externalStorageDirectory.getAbsolutePath());
|
||||
} else {
|
||||
constants.put(ExternalStorageDirectoryPath, null);
|
||||
}
|
||||
|
||||
File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null);
|
||||
if (externalDirectory != null) {
|
||||
constants.put(ExternalDirectoryPath, externalDirectory.getAbsolutePath());
|
||||
} else {
|
||||
constants.put(ExternalDirectoryPath, null);
|
||||
}
|
||||
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user