From 22721a013bdd10d5eb395ba18453585f5f3f1f7f Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 31 Aug 2020 23:07:20 +0200 Subject: rebuild the ios platform and the plugins --- .../src/android/FirebasePlugin.java | 2441 ++++++++++++++++++++ .../src/android/FirebasePluginMessageReceiver.java | 28 + .../FirebasePluginMessageReceiverManager.java | 41 + .../android/FirebasePluginMessagingService.java | 348 +++ .../src/android/JavaScriptException.java | 45 + .../src/android/OnNotificationOpenReceiver.java | 36 + .../src/android/build.gradle | 48 + .../src/android/colors.xml | 3 + .../android/cordova-plugin-firebase-strings.xml | 5 + .../src/ios/AppDelegate+FirebasePlugin.h | 11 + .../src/ios/AppDelegate+FirebasePlugin.m | 557 +++++ .../src/ios/FirebasePlugin.h | 119 + .../src/ios/FirebasePlugin.m | 1757 ++++++++++++++ .../src/ios/FirebasePluginMessageReceiver.h | 5 + .../src/ios/FirebasePluginMessageReceiver.m | 17 + .../src/ios/FirebasePluginMessageReceiverManager.h | 6 + .../src/ios/FirebasePluginMessageReceiverManager.m | 24 + .../src/ios/GoogleService-Info.plist | 6 + 18 files changed, 5497 insertions(+) create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePlugin.java create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessageReceiver.java create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessageReceiverManager.java create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessagingService.java create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/JavaScriptException.java create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/OnNotificationOpenReceiver.java create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/build.gradle create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/colors.xml create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/android/cordova-plugin-firebase-strings.xml create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/AppDelegate+FirebasePlugin.h create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/AppDelegate+FirebasePlugin.m create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePlugin.h create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePlugin.m create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiver.h create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiver.m create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiverManager.h create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiverManager.m create mode 100644 StoneIsland/plugins/cordova-plugin-firebasex/src/ios/GoogleService-Info.plist (limited to 'StoneIsland/plugins/cordova-plugin-firebasex/src') diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePlugin.java b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePlugin.java new file mode 100644 index 00000000..d81419e9 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePlugin.java @@ -0,0 +1,2441 @@ +package org.apache.cordova.firebase; + +import android.app.Activity; +import android.app.NotificationManager; +import android.app.NotificationChannel; +import android.content.ContentResolver; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.media.RingtoneManager; +import android.net.Uri; +import android.media.AudioAttributes; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import android.util.Base64; +import android.util.Log; + +import com.google.firebase.crashlytics.FirebaseCrashlytics; + +import com.google.android.gms.auth.api.Auth; +import com.google.android.gms.auth.api.signin.GoogleSignIn; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInClient; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.common.api.ApiException; +import com.google.android.gms.common.api.CommonStatusCodes; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.FirebaseApp; +import com.google.firebase.analytics.FirebaseAnalytics; +import com.google.firebase.auth.AuthCredential; +import com.google.firebase.auth.AuthResult; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.auth.GetTokenResult; +import com.google.firebase.auth.GoogleAuthProvider; +import com.google.firebase.auth.OAuthProvider; +import com.google.firebase.auth.UserProfileChangeRequest; +import com.google.firebase.firestore.CollectionReference; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.firebase.firestore.QuerySnapshot; +import com.google.firebase.firestore.Query.Direction; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.remoteconfig.FirebaseRemoteConfig; +import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo; +import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; +import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue; +import com.google.firebase.perf.FirebasePerformance; +import com.google.firebase.perf.metrics.Trace; + + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.List; + +// Firebase PhoneAuth +import java.util.concurrent.TimeUnit; + +import com.google.firebase.FirebaseException; +import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException; +import com.google.firebase.FirebaseTooManyRequestsException; +import com.google.firebase.auth.PhoneAuthCredential; +import com.google.firebase.auth.PhoneAuthProvider; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import static android.content.Context.MODE_PRIVATE; + +public class FirebasePlugin extends CordovaPlugin { + + protected static FirebasePlugin instance = null; + private FirebaseAnalytics mFirebaseAnalytics; + private FirebaseCrashlytics firebaseCrashlytics; + private FirebaseFirestore firestore; + private Gson gson; + private FirebaseAuth.AuthStateListener authStateListener; + private boolean authStateChangeListenerInitialized = false; + private static CordovaInterface cordovaInterface = null; + protected static Context applicationContext = null; + private static Activity cordovaActivity = null; + + protected static final String TAG = "FirebasePlugin"; + protected static final String JS_GLOBAL_NAMESPACE = "FirebasePlugin."; + protected static final String KEY = "badge"; + protected static final int GOOGLE_SIGN_IN = 0x1; + protected static final String SETTINGS_NAME = "settings"; + private static final String CRASHLYTICS_COLLECTION_ENABLED = "firebase_crashlytics_collection_enabled"; + private static final String ANALYTICS_COLLECTION_ENABLED = "firebase_analytics_collection_enabled"; + private static final String PERFORMANCE_COLLECTION_ENABLED = "firebase_performance_collection_enabled"; + + private static boolean inBackground = true; + private static ArrayList notificationStack = null; + private static CallbackContext notificationCallbackContext; + private static CallbackContext tokenRefreshCallbackContext; + private static CallbackContext activityResultCallbackContext; + private static CallbackContext authResultCallbackContext; + + private static NotificationChannel defaultNotificationChannel = null; + public static String defaultChannelId = null; + public static String defaultChannelName = null; + + private Map authCredentials = new HashMap(); + private Map authProviders = new HashMap(); + + @Override + protected void pluginInitialize() { + instance = this; + cordovaActivity = this.cordova.getActivity(); + applicationContext = cordovaActivity.getApplicationContext(); + final Bundle extras = cordovaActivity.getIntent().getExtras(); + FirebasePlugin.cordovaInterface = this.cordova; + firebaseCrashlytics = FirebaseCrashlytics.getInstance(); + this.cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + Log.d(TAG, "Starting Firebase plugin"); + + if(getMetaDataFromManifest(CRASHLYTICS_COLLECTION_ENABLED)){ + setPreference(CRASHLYTICS_COLLECTION_ENABLED, true); + } + + if(getMetaDataFromManifest(ANALYTICS_COLLECTION_ENABLED)){ + setPreference(ANALYTICS_COLLECTION_ENABLED, true); + } + + if(getMetaDataFromManifest(PERFORMANCE_COLLECTION_ENABLED)){ + setPreference(PERFORMANCE_COLLECTION_ENABLED, true); + } + + FirebaseApp.initializeApp(applicationContext); + mFirebaseAnalytics = FirebaseAnalytics.getInstance(applicationContext); + + authStateListener = new AuthStateListener(); + FirebaseAuth.getInstance().addAuthStateListener(authStateListener); + + firestore = FirebaseFirestore.getInstance(); + gson = new Gson(); + + if (extras != null && extras.size() > 1) { + if (FirebasePlugin.notificationStack == null) { + FirebasePlugin.notificationStack = new ArrayList(); + } + if (extras.containsKey("google.message_id")) { + extras.putString("messageType", "notification"); + extras.putString("tap", "background"); + notificationStack.add(extras); + Log.d(TAG, "Notification message found on init: " + extras.toString()); + } + } + defaultChannelId = getStringResource("default_notification_channel_id"); + defaultChannelName = getStringResource("default_notification_channel_name"); + createDefaultChannel(); + }catch (Exception e){ + handleExceptionWithoutContext(e); + } + } + }); + } + + @Override + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + try{ + if (action.equals("getId")) { + this.getId(callbackContext); + return true; + } else if (action.equals("getToken")) { + this.getToken(callbackContext); + return true; + } else if (action.equals("hasPermission")) { + this.hasPermission(callbackContext); + return true; + }else if (action.equals("subscribe")) { + this.subscribe(callbackContext, args.getString(0)); + return true; + } else if (action.equals("unsubscribe")) { + this.unsubscribe(callbackContext, args.getString(0)); + return true; + } else if (action.equals("isAutoInitEnabled")) { + isAutoInitEnabled(callbackContext); + return true; + } else if (action.equals("setAutoInitEnabled")) { + setAutoInitEnabled(callbackContext, args.getBoolean(0)); + return true; + } else if (action.equals("unregister")) { + this.unregister(callbackContext); + return true; + } else if (action.equals("onMessageReceived")) { + this.onMessageReceived(callbackContext); + return true; + } else if (action.equals("onTokenRefresh")) { + this.onTokenRefresh(callbackContext); + return true; + } else if (action.equals("logEvent")) { + this.logEvent(callbackContext, args.getString(0), args.getJSONObject(1)); + return true; + } else if (action.equals("logError")) { + this.logError(callbackContext, args); + return true; + }else if(action.equals("setCrashlyticsUserId")){ + this.setCrashlyticsUserId(callbackContext, args.getString(0)); + return true; + } else if (action.equals("setScreenName")) { + this.setScreenName(callbackContext, args.getString(0)); + return true; + } else if (action.equals("setUserId")) { + this.setUserId(callbackContext, args.getString(0)); + return true; + } else if (action.equals("setUserProperty")) { + this.setUserProperty(callbackContext, args.getString(0), args.getString(1)); + return true; + } else if (action.equals("activateFetched")) { + this.activateFetched(callbackContext); + return true; + } else if (action.equals("fetch")) { + if (args.length() > 0) { + this.fetch(callbackContext, args.getLong(0)); + } else { + this.fetch(callbackContext); + } + return true; + } else if (action.equals("getByteArray")) { + this.getByteArray(callbackContext, args.getString(0)); + return true; + } else if (action.equals("getValue")) { + this.getValue(callbackContext, args.getString(0)); + return true; + } else if (action.equals("getInfo")) { + this.getInfo(callbackContext); + return true; + } else if (action.equals("setConfigSettings")) { + this.setConfigSettings(callbackContext, args.getJSONObject(0)); + return true; + } else if (action.equals("setDefaults")) { + this.setDefaults(callbackContext, args.getJSONObject(0)); + return true; + } else if (action.equals("verifyPhoneNumber")) { + this.verifyPhoneNumber(callbackContext, args); + return true; + } else if (action.equals("authenticateUserWithGoogle")) { + this.authenticateUserWithGoogle(callbackContext, args); + return true; + } else if (action.equals("authenticateUserWithApple")) { + this.authenticateUserWithApple(callbackContext, args); + return true; + } else if (action.equals("createUserWithEmailAndPassword")) { + this.createUserWithEmailAndPassword(callbackContext, args); + return true; + } else if (action.equals("signInUserWithEmailAndPassword")) { + this.signInUserWithEmailAndPassword(callbackContext, args); + return true; + } else if (action.equals("signInUserWithCustomToken")) { + this.signInUserWithCustomToken(callbackContext, args); + return true; + } else if (action.equals("signInUserAnonymously")) { + this.signInUserAnonymously(callbackContext); + return true; + } else if (action.equals("signInWithCredential")) { + this.signInWithCredential(callbackContext, args); + return true; + } else if (action.equals("linkUserWithCredential")) { + this.linkUserWithCredential(callbackContext, args); + return true; + } else if (action.equals("reauthenticateWithCredential")) { + this.reauthenticateWithCredential(callbackContext, args); + return true; + } else if (action.equals("isUserSignedIn")) { + this.isUserSignedIn(callbackContext, args); + return true; + } else if (action.equals("signOutUser")) { + this.signOutUser(callbackContext, args); + return true; + } else if (action.equals("getCurrentUser")) { + this.getCurrentUser(callbackContext, args); + return true; + } else if (action.equals("reloadCurrentUser")) { + this.reloadCurrentUser(callbackContext, args); + return true; + } else if (action.equals("updateUserProfile")) { + this.updateUserProfile(callbackContext, args); + return true; + } else if (action.equals("updateUserEmail")) { + this.updateUserEmail(callbackContext, args); + return true; + } else if (action.equals("sendUserEmailVerification")) { + this.sendUserEmailVerification(callbackContext, args); + return true; + } else if (action.equals("updateUserPassword")) { + this.updateUserPassword(callbackContext, args); + return true; + } else if (action.equals("sendUserPasswordResetEmail")) { + this.sendUserPasswordResetEmail(callbackContext, args); + return true; + } else if (action.equals("deleteUser")) { + this.deleteUser(callbackContext, args); + return true; + } else if (action.equals("startTrace")) { + this.startTrace(callbackContext, args.getString(0)); + return true; + } else if (action.equals("incrementCounter")) { + this.incrementCounter(callbackContext, args.getString(0), args.getString(1)); + return true; + } else if (action.equals("stopTrace")) { + this.stopTrace(callbackContext, args.getString(0)); + return true; + } else if (action.equals("setAnalyticsCollectionEnabled")) { + this.setAnalyticsCollectionEnabled(callbackContext, args.getBoolean(0)); + return true; + } else if (action.equals("isAnalyticsCollectionEnabled")) { + this.isAnalyticsCollectionEnabled(callbackContext); + return true; + } else if (action.equals("setPerformanceCollectionEnabled")) { + this.setPerformanceCollectionEnabled(callbackContext, args.getBoolean(0)); + return true; + } else if (action.equals("isPerformanceCollectionEnabled")) { + this.isPerformanceCollectionEnabled(callbackContext); + return true; + } else if (action.equals("setCrashlyticsCollectionEnabled")) { + this.setCrashlyticsCollectionEnabled(callbackContext, args.getBoolean(0)); + return true; + } else if (action.equals("isCrashlyticsCollectionEnabled")) { + this.isCrashlyticsCollectionEnabled(callbackContext); + return true; + } else if (action.equals("clearAllNotifications")) { + this.clearAllNotifications(callbackContext); + return true; + } else if (action.equals("logMessage")) { + logMessage(args, callbackContext); + return true; + } else if (action.equals("sendCrash")) { + sendCrash(args, callbackContext); + return true; + } else if (action.equals("createChannel")) { + this.createChannel(callbackContext, args.getJSONObject(0)); + return true; + } else if (action.equals("deleteChannel")) { + this.deleteChannel(callbackContext, args.getString(0)); + return true; + } else if (action.equals("listChannels")) { + this.listChannels(callbackContext); + return true; + } else if (action.equals("setDefaultChannel")) { + this.setDefaultChannel(callbackContext, args.getJSONObject(0)); + return true; + } else if (action.equals("addDocumentToFirestoreCollection")) { + this.addDocumentToFirestoreCollection(args, callbackContext); + return true; + } else if (action.equals("setDocumentInFirestoreCollection")) { + this.setDocumentInFirestoreCollection(args, callbackContext); + return true; + } else if (action.equals("updateDocumentInFirestoreCollection")) { + this.updateDocumentInFirestoreCollection(args, callbackContext); + return true; + } else if (action.equals("deleteDocumentFromFirestoreCollection")) { + this.deleteDocumentFromFirestoreCollection(args, callbackContext); + return true; + } else if (action.equals("documentExistsInFirestoreCollection")) { + this.documentExistsInFirestoreCollection(args, callbackContext); + return true; + } else if (action.equals("fetchDocumentInFirestoreCollection")) { + this.fetchDocumentInFirestoreCollection(args, callbackContext); + return true; + } else if (action.equals("fetchFirestoreCollection")) { + this.fetchFirestoreCollection(args, callbackContext); + return true; + } else if (action.equals("grantPermission") + || action.equals("setBadgeNumber") + || action.equals("getBadgeNumber") + ) { + // Stubs for other platform methods + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, true)); + return true; + }else{ + callbackContext.error("Invalid action: " + action); + return false; + } + }catch(Exception e){ + handleExceptionWithContext(e, callbackContext); + } + return false; + } + + @Override + public void onPause(boolean multitasking) { + FirebasePlugin.inBackground = true; + } + + @Override + public void onResume(boolean multitasking) { + FirebasePlugin.inBackground = false; + } + + @Override + public void onReset() { + FirebasePlugin.notificationCallbackContext = null; + FirebasePlugin.tokenRefreshCallbackContext = null; + FirebasePlugin.activityResultCallbackContext = null; + FirebasePlugin.authResultCallbackContext = null; + } + + @Override + public void onDestroy() { + FirebaseAuth.getInstance().removeAuthStateListener(authStateListener); + instance = null; + cordovaActivity = null; + cordovaInterface = null; + applicationContext = null; + super.onDestroy(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + try { + switch (requestCode) { + case GOOGLE_SIGN_IN: + Task task = GoogleSignIn.getSignedInAccountFromIntent(data); + GoogleSignInAccount acct; + try{ + acct = task.getResult(ApiException.class); + }catch (ApiException ae){ + if(ae.getStatusCode() == 10){ + throw new Exception("Unknown server client ID"); + }else{ + throw new Exception(CommonStatusCodes.getStatusCodeString(ae.getStatusCode())); + } + } + AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null); + String id = FirebasePlugin.instance.saveAuthCredential(credential); + + JSONObject returnResults = new JSONObject(); + returnResults.put("instantVerification", true); + returnResults.put("id", id); + FirebasePlugin.activityResultCallbackContext.success(returnResults); + break; + } + } catch (Exception e) { + handleExceptionWithContext(e, FirebasePlugin.activityResultCallbackContext); + } + } + + /** + * Get a string from resources without importing the .R package + * + * @param name Resource Name + * @return Resource + */ + private String getStringResource(String name) { + return applicationContext.getString( + applicationContext.getResources().getIdentifier( + name, "string", applicationContext.getPackageName() + ) + ); + } + + private void onMessageReceived(final CallbackContext callbackContext) { + FirebasePlugin.notificationCallbackContext = callbackContext; + if (FirebasePlugin.notificationStack != null) { + for (Bundle bundle : FirebasePlugin.notificationStack) { + FirebasePlugin.sendMessage(bundle, applicationContext); + } + FirebasePlugin.notificationStack.clear(); + } + } + + private void onTokenRefresh(final CallbackContext callbackContext) { + FirebasePlugin.tokenRefreshCallbackContext = callbackContext; + + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String currentToken = FirebaseInstanceId.getInstance().getToken(); + if (currentToken != null) { + FirebasePlugin.sendToken(currentToken); + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public static void sendMessage(Bundle bundle, Context context) { + if (!FirebasePlugin.hasNotificationsCallback()) { + String packageName = context.getPackageName(); + if (FirebasePlugin.notificationStack == null) { + FirebasePlugin.notificationStack = new ArrayList(); + } + notificationStack.add(bundle); + + return; + } + + final CallbackContext callbackContext = FirebasePlugin.notificationCallbackContext; + if(bundle != null){ + // Pass the message bundle to the receiver manager so any registered receivers can decide to handle it + boolean wasHandled = FirebasePluginMessageReceiverManager.sendMessage(bundle); + if (wasHandled) { + Log.d(TAG, "Message bundle was handled by a registered receiver"); + }else if (callbackContext != null) { + JSONObject json = new JSONObject(); + Set keys = bundle.keySet(); + for (String key : keys) { + try { + json.put(key, bundle.get(key)); + } catch (JSONException e) { + handleExceptionWithContext(e, callbackContext); + return; + } + } + + PluginResult pluginresult = new PluginResult(PluginResult.Status.OK, json); + pluginresult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginresult); + } + } + } + + public static void sendToken(String token) { + if (FirebasePlugin.tokenRefreshCallbackContext == null) { + return; + } + + final CallbackContext callbackContext = FirebasePlugin.tokenRefreshCallbackContext; + if (callbackContext != null && token != null) { + PluginResult pluginresult = new PluginResult(PluginResult.Status.OK, token); + pluginresult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginresult); + } + } + + public static boolean inBackground() { + return FirebasePlugin.inBackground; + } + + public static boolean hasNotificationsCallback() { + return FirebasePlugin.notificationCallbackContext != null; + } + + @Override + public void onNewIntent(Intent intent) { + try { + super.onNewIntent(intent); + final Bundle data = intent.getExtras(); + if (data != null && data.containsKey("google.message_id")) { + data.putString("messageType", "notification"); + data.putString("tap", "background"); + Log.d(TAG, "Notification message on new intent: " + data.toString()); + FirebasePlugin.sendMessage(data, applicationContext); + } + }catch (Exception e){ + handleExceptionWithoutContext(e); + } + } + + + private void getId(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String id = FirebaseInstanceId.getInstance().getId(); + callbackContext.success(id); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void getToken(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String token = FirebaseInstanceId.getInstance().getToken(); + callbackContext.success(token); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void hasPermission(final CallbackContext callbackContext) { + if(cordovaActivity == null) return; + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(cordovaActivity); + boolean areNotificationsEnabled = notificationManagerCompat.areNotificationsEnabled(); + callbackContext.success(areNotificationsEnabled ? 1 : 0); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void subscribe(final CallbackContext callbackContext, final String topic) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + handleTaskOutcome(FirebaseMessaging.getInstance().subscribeToTopic(topic), callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void unsubscribe(final CallbackContext callbackContext, final String topic) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + handleTaskOutcome(FirebaseMessaging.getInstance().unsubscribeFromTopic(topic), callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void unregister(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseInstanceId.getInstance().deleteInstanceId(); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void isAutoInitEnabled(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + boolean isEnabled = FirebaseMessaging.getInstance().isAutoInitEnabled(); + callbackContext.success(isEnabled ? 1 : 0); + } catch (Exception e) { + logExceptionToCrashlytics(e); + callbackContext.error(e.getMessage()); + } + } + }); + } + + private void setAutoInitEnabled(final CallbackContext callbackContext, final boolean enabled) { + final FirebasePlugin self = this; + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseMessaging.getInstance().setAutoInitEnabled(enabled); + callbackContext.success(); + } catch (Exception e) { + logExceptionToCrashlytics(e); + e.printStackTrace(); + callbackContext.error(e.getMessage()); + } + } + }); + } + + private void logEvent(final CallbackContext callbackContext, final String name, final JSONObject params) + throws JSONException { + final Bundle bundle = new Bundle(); + Iterator iter = params.keys(); + while (iter.hasNext()) { + String key = (String) iter.next(); + Object value = params.get(key); + + if (value instanceof Integer || value instanceof Double) { + bundle.putFloat(key, ((Number) value).floatValue()); + } else { + bundle.putString(key, value.toString()); + } + } + + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + mFirebaseAnalytics.logEvent(name, bundle); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void logError(final CallbackContext callbackContext, final JSONArray args) throws JSONException { + final String message = args.getString(0); + + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + if(isCrashlyticsEnabled()) { + // We can optionally be passed a stack trace generated by stacktrace.js. + if (args.length() == 2) { + JSONArray stackTrace = args.getJSONArray(1); + StackTraceElement[] trace = new StackTraceElement[stackTrace.length()]; + for (int i = 0; i < stackTrace.length(); i++) { + JSONObject elem = stackTrace.getJSONObject(i); + trace[i] = new StackTraceElement( + "", + elem.optString("functionName", "(anonymous function)"), + elem.optString("fileName", "(unknown file)"), + elem.optInt("lineNumber", -1) + ); + } + + Exception e = new JavaScriptException(message); + e.setStackTrace(trace); + logExceptionToCrashlytics(e); + } else { + logExceptionToCrashlytics(new JavaScriptException(message)); + } + + Log.e(TAG, message); + callbackContext.success(1); + }else{ + callbackContext.error("Cannot log error - Crashlytics collection is disabled"); + } + } catch (Exception e) { + logExceptionToCrashlytics(e); + callbackContext.error(e.getMessage()); + } + } + }); + } + + private void logMessage(final JSONArray data, + final CallbackContext callbackContext) { + + if(isCrashlyticsEnabled()){ + String message = data.optString(0); + logMessageToCrashlytics(message); + callbackContext.success(); + }else{ + callbackContext.error("Cannot log message - Crashlytics collection is disabled"); + } + } + + private void sendCrash(final JSONArray data, + final CallbackContext callbackContext) { + + cordovaActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + throw new RuntimeException("This is a crash"); + } + }); + } + + + private void setCrashlyticsUserId(final CallbackContext callbackContext, final String userId) { + cordovaActivity.runOnUiThread(new Runnable() { + public void run() { + try { + if(isCrashlyticsEnabled()){ + firebaseCrashlytics.setUserId(userId); + callbackContext.success(); + }else{ + callbackContext.error("Cannot set Crashlytics user ID - Crashlytics collection is disabled"); + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void setScreenName(final CallbackContext callbackContext, final String name) { + // This must be called on the main thread + cordovaActivity.runOnUiThread(new Runnable() { + public void run() { + try { + mFirebaseAnalytics.setCurrentScreen(cordovaActivity, name, null); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void setUserId(final CallbackContext callbackContext, final String id) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + mFirebaseAnalytics.setUserId(id); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void setUserProperty(final CallbackContext callbackContext, final String name, final String value) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + mFirebaseAnalytics.setUserProperty(name, value); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void activateFetched(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + final boolean activated = FirebaseRemoteConfig.getInstance().activateFetched(); + callbackContext.success(String.valueOf(activated)); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void fetch(CallbackContext callbackContext) { + fetch(callbackContext, FirebaseRemoteConfig.getInstance().fetch()); + } + + private void fetch(CallbackContext callbackContext, long cacheExpirationSeconds) { + fetch(callbackContext, FirebaseRemoteConfig.getInstance().fetch(cacheExpirationSeconds)); + } + + private void fetch(final CallbackContext callbackContext, final Task task) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + handleTaskOutcome(task, callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void getByteArray(final CallbackContext callbackContext, final String key) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + byte[] bytes = FirebaseRemoteConfig.getInstance().getByteArray(key); + JSONObject object = new JSONObject(); + object.put("base64", Base64.encodeToString(bytes, Base64.DEFAULT)); + object.put("array", new JSONArray(bytes)); + callbackContext.success(object); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void getValue(final CallbackContext callbackContext, final String key) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseRemoteConfigValue value = FirebaseRemoteConfig.getInstance().getValue(key); + callbackContext.success(value.asString()); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void getInfo(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseRemoteConfigInfo remoteConfigInfo = FirebaseRemoteConfig.getInstance().getInfo(); + JSONObject info = new JSONObject(); + + JSONObject settings = new JSONObject(); + settings.put("developerModeEnabled", remoteConfigInfo.getConfigSettings().isDeveloperModeEnabled()); + info.put("configSettings", settings); + + info.put("fetchTimeMillis", remoteConfigInfo.getFetchTimeMillis()); + info.put("lastFetchStatus", remoteConfigInfo.getLastFetchStatus()); + + callbackContext.success(info); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void setConfigSettings(final CallbackContext callbackContext, final JSONObject config) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + boolean devMode = config.getBoolean("developerModeEnabled"); + FirebaseRemoteConfigSettings.Builder settings = new FirebaseRemoteConfigSettings.Builder() + .setDeveloperModeEnabled(devMode); + FirebaseRemoteConfig.getInstance().setConfigSettings(settings.build()); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void setDefaults(final CallbackContext callbackContext, final JSONObject defaults) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseRemoteConfig.getInstance().setDefaults(defaultsToMap(defaults)); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private static Map defaultsToMap(JSONObject object) throws JSONException { + final Map map = new HashMap(); + + for (Iterator keys = object.keys(); keys.hasNext(); ) { + String key = keys.next(); + Object value = object.get(key); + + if (value instanceof Integer) { + //setDefaults() should take Longs + value = new Long((Integer) value); + } else if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + if (array.length() == 1 && array.get(0) instanceof String) { + //parse byte[] as Base64 String + value = Base64.decode(array.getString(0), Base64.DEFAULT); + } else { + //parse byte[] as numeric array + byte[] bytes = new byte[array.length()]; + for (int i = 0; i < array.length(); i++) + bytes[i] = (byte) array.getInt(i); + value = bytes; + } + } + + map.put(key, value); + } + return map; + } + + + public void isUserSignedIn(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + boolean isSignedIn = FirebaseAuth.getInstance().getCurrentUser() != null; + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, isSignedIn)); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void signOutUser(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + // Sign out of Firebase + FirebaseAuth.getInstance().signOut(); + + // Try to sign out of Google + try{ + GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).build(); + GoogleSignInClient mGoogleSignInClient = GoogleSignIn.getClient(cordovaActivity, gso); + handleTaskOutcome(mGoogleSignInClient.signOut(), callbackContext); + }catch(Exception googleSignOutException){ + callbackContext.success(); + } + + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void getCurrentUser(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + extractAndReturnUserInfo(callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void reloadCurrentUser(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + user.reload() + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Void aVoid) { + try { + extractAndReturnUserInfo(callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void extractAndReturnUserInfo(final CallbackContext callbackContext) throws Exception{ + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + JSONObject returnResults = new JSONObject(); + returnResults.put("name", user.getDisplayName()); + returnResults.put("email", user.getEmail()); + returnResults.put("emailIsVerified", user.isEmailVerified()); + returnResults.put("phoneNumber", user.getPhoneNumber()); + returnResults.put("photoUrl", user.getPhotoUrl() == null ? null : user.getPhotoUrl().toString()); + returnResults.put("uid", user.getUid()); + returnResults.put("providerId", user.getIdToken(false).getResult().getSignInProvider()); + returnResults.put("isAnonymous", user.isAnonymous()); + + user.getIdToken(true).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(GetTokenResult result) { + try { + String idToken = result.getToken(); + returnResults.put("idToken", idToken); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, returnResults)); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void updateUserProfile(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + + JSONObject profile = args.getJSONObject(0); + UserProfileChangeRequest profileUpdates; + if(profile.has("name") && profile.has("photoUri")){ + profileUpdates = new UserProfileChangeRequest.Builder() + .setDisplayName(profile.getString("name")) + .setPhotoUri(Uri.parse(profile.getString("photoUri"))) + .build(); + }else if(profile.has("name")){ + profileUpdates = new UserProfileChangeRequest.Builder() + .setDisplayName(profile.getString("name")) + .build(); + }else if(profile.has("photoUri")){ + profileUpdates = new UserProfileChangeRequest.Builder() + .setPhotoUri(Uri.parse(profile.getString("photoUri"))) + .build(); + }else{ + callbackContext.error("'name' and/or 'photoUri' keys must be specified in the profile object"); + return; + } + + handleTaskOutcome(user.updateProfile(profileUpdates), callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void updateUserEmail(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + + String email = args.getString(0); + handleTaskOutcome(user.updateEmail(email), callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void sendUserEmailVerification(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + + handleTaskOutcome(user.sendEmailVerification(), callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void updateUserPassword(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + + String password = args.getString(0); + handleTaskOutcome(user.updatePassword(password), callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void sendUserPasswordResetEmail(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseAuth auth = FirebaseAuth.getInstance(); + String email = args.getString(0); + handleTaskOutcome(auth.sendPasswordResetEmail(email), callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void deleteUser(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + handleTaskOutcome(user.delete(), callbackContext); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void reauthenticateWithCredential(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if(user == null){ + callbackContext.error("No user is currently signed"); + return; + } + + JSONObject jsonCredential = args.getJSONObject(0); + if(!FirebasePlugin.instance.isValidJsonCredential(jsonCredential)){ + callbackContext.error("No auth credentials specified"); + return; + } + + AuthCredential authCredential = FirebasePlugin.instance.obtainAuthCredential(jsonCredential); + if(authCredential != null){ + handleTaskOutcome(user.reauthenticate(authCredential), callbackContext); + return; + } + + OAuthProvider authProvider = FirebasePlugin.instance.obtainAuthProvider(jsonCredential); + if(authProvider != null){ + FirebasePlugin.instance.authResultCallbackContext = callbackContext; + user.startActivityForReauthenticateWithProvider(FirebasePlugin.cordovaActivity, authProvider) + .addOnSuccessListener(new AuthResultOnSuccessListener()) + .addOnFailureListener(new AuthResultOnFailureListener()); + return; + } + + //ELSE + callbackContext.error("Specified native auth credential id does not exist"); + + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + + + public void signInWithCredential(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + JSONObject jsonCredential = args.getJSONObject(0); + if(!FirebasePlugin.instance.isValidJsonCredential(jsonCredential)){ + callbackContext.error("No auth credentials specified"); + return; + } + + AuthCredential authCredential = FirebasePlugin.instance.obtainAuthCredential(jsonCredential); + if(authCredential != null){ + FirebaseAuth.getInstance().signInWithCredential(authCredential).addOnCompleteListener(cordova.getActivity(), new AuthResultOnCompleteListener(callbackContext)); + return; + } + + OAuthProvider authProvider = FirebasePlugin.instance.obtainAuthProvider(jsonCredential); + if(authProvider != null){ + FirebasePlugin.instance.authResultCallbackContext = callbackContext; + FirebaseAuth.getInstance().startActivityForSignInWithProvider(FirebasePlugin.cordovaActivity, authProvider) + .addOnSuccessListener(new AuthResultOnSuccessListener()) + .addOnFailureListener(new AuthResultOnFailureListener()); + return; + } + + //ELSE + callbackContext.error("Specified native auth credential id does not exist"); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void linkUserWithCredential(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + JSONObject jsonCredential = args.getJSONObject(0); + if(!FirebasePlugin.instance.isValidJsonCredential(jsonCredential)){ + callbackContext.error("No auth credentials specified"); + return; + } + + AuthCredential authCredential = FirebasePlugin.instance.obtainAuthCredential(jsonCredential); + if(authCredential != null){ + FirebaseAuth.getInstance().getCurrentUser().linkWithCredential(authCredential).addOnCompleteListener(cordova.getActivity(), new AuthResultOnCompleteListener(callbackContext)); + return; + } + + //ELSE + callbackContext.error("Specified native auth credential id does not exist"); + + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private boolean isValidJsonCredential(JSONObject jsonCredential) throws JSONException{ + return jsonCredential.has("id") || (jsonCredential.has("verificationId") && jsonCredential.has("code")); + } + + private PhoneAuthProvider.OnVerificationStateChangedCallbacks mCallbacks; + + public void verifyPhoneNumber( + final CallbackContext callbackContext, + final JSONArray args + ) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { + @Override + public void onVerificationCompleted(PhoneAuthCredential credential) { + // This callback will be invoked in two situations: + // 1 - Instant verification. In some cases the phone number can be instantly + // verified without needing to send or enter a verification code. + // 2 - Auto-retrieval. On some devices Google Play services can automatically + // detect the incoming verification SMS and perform verificaiton without + // user action. + Log.d(TAG, "success: verifyPhoneNumber.onVerificationCompleted"); + + String id = FirebasePlugin.instance.saveAuthCredential((AuthCredential) credential); + + JSONObject returnResults = new JSONObject(); + try { + returnResults.put("instantVerification", true); + returnResults.put("id", id); + } catch(JSONException e){ + handleExceptionWithContext(e, callbackContext); + return; + } + PluginResult pluginresult = new PluginResult(PluginResult.Status.OK, returnResults); + pluginresult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginresult); + } + + @Override + public void onVerificationFailed(FirebaseException e) { + // This callback is invoked in an invalid request for verification is made, + // for instance if the the phone number format is not valid. + Log.w(TAG, "failed: verifyPhoneNumber.onVerificationFailed ", e); + + String errorMsg; + if (e instanceof FirebaseAuthInvalidCredentialsException) { + // Invalid request + errorMsg = "Invalid phone number"; + } else if (e instanceof FirebaseTooManyRequestsException) { + // The SMS quota for the project has been exceeded + errorMsg = "The SMS quota for the project has been exceeded"; + }else{ + errorMsg = e.getMessage(); + } + callbackContext.error(errorMsg); + } + + @Override + public void onCodeSent(String verificationId, PhoneAuthProvider.ForceResendingToken token) { + // The SMS verification code has been sent to the provided phone number, we + // now need to ask the user to enter the code and then construct a credential + // by combining the code with a verification ID [(in app)]. + Log.d(TAG, "success: verifyPhoneNumber.onCodeSent"); + + JSONObject returnResults = new JSONObject(); + try { + returnResults.put("verificationId", verificationId); + returnResults.put("instantVerification", false); + } catch (JSONException e) { + handleExceptionWithContext(e, callbackContext); + return; + } + PluginResult pluginresult = new PluginResult(PluginResult.Status.OK, returnResults); + pluginresult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginresult); + } + }; + + String number = args.getString(0); + int timeOutDuration = args.getInt(1); + String smsCode = args.getString(2); + + if(smsCode != null && smsCode != "null"){ + FirebaseAuth.getInstance().getFirebaseAuthSettings().setAutoRetrievedSmsCodeForPhoneNumber(number, smsCode); + } + + PhoneAuthProvider.getInstance().verifyPhoneNumber(number, // Phone number to verify + timeOutDuration, // Timeout duration + TimeUnit.SECONDS, // Unit of timeout + cordovaActivity, // Activity (for callback binding) + mCallbacks); // OnVerificationStateChangedCallbacks + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void createUserWithEmailAndPassword(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String email = args.getString(0); + String password = args.getString(1); + + if(email == null || email.equals("")){ + callbackContext.error("User email address must be specified"); + return; + } + + if(password == null || password.equals("")){ + callbackContext.error("User password must be specified"); + return; + } + + FirebaseAuth.getInstance().createUserWithEmailAndPassword(email, password).addOnCompleteListener(cordova.getActivity(), new AuthResultOnCompleteListener(callbackContext)); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void signInUserWithEmailAndPassword(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String email = args.getString(0); + String password = args.getString(1); + + if(email == null || email.equals("")){ + callbackContext.error("User email address must be specified"); + return; + } + + if(password == null || password.equals("")){ + callbackContext.error("User password must be specified"); + return; + } + + FirebaseAuth.getInstance().signInWithEmailAndPassword(email, password).addOnCompleteListener(cordova.getActivity(), new AuthResultOnCompleteListener(callbackContext)); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + + public void authenticateUserWithGoogle(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String clientId = args.getString(0); + + GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(clientId) + .requestEmail() + .build(); + + GoogleSignInClient mGoogleSignInClient = GoogleSignIn.getClient(FirebasePlugin.instance.cordovaActivity, gso); + Intent signInIntent = mGoogleSignInClient.getSignInIntent(); + FirebasePlugin.activityResultCallbackContext = callbackContext; + FirebasePlugin.instance.cordovaInterface.startActivityForResult(FirebasePlugin.instance, signInIntent, GOOGLE_SIGN_IN); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void authenticateUserWithApple(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String locale = args.getString(0); + OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com"); + if(locale != null){ + provider.addCustomParameter("locale", locale); + } + Task pending = FirebaseAuth.getInstance().getPendingAuthResult(); + if (pending != null) { + callbackContext.error("Auth result is already pending"); + pending + .addOnSuccessListener(new AuthResultOnSuccessListener()) + .addOnFailureListener(new AuthResultOnFailureListener()); + } else { + String id = FirebasePlugin.instance.saveAuthProvider(provider.build());; + JSONObject returnResults = new JSONObject(); + returnResults.put("instantVerification", true); + returnResults.put("id", id); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, returnResults)); + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void signInUserWithCustomToken(final CallbackContext callbackContext, final JSONArray args){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String customToken = args.getString(0); + + if(customToken == null || customToken.equals("")){ + callbackContext.error("Custom token must be specified"); + return; + } + + FirebaseAuth.getInstance().signInWithCustomToken(customToken).addOnCompleteListener(cordova.getActivity(), new AuthResultOnCompleteListener(callbackContext)); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void signInUserAnonymously(final CallbackContext callbackContext){ + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebaseAuth.getInstance().signInAnonymously().addOnCompleteListener(cordova.getActivity(), new AuthResultOnCompleteListener(callbackContext)); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + // + // Firebase Performace + // + + private HashMap traces = new HashMap(); + + private void startTrace(final CallbackContext callbackContext, final String name) { + final FirebasePlugin self = this; + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + + Trace myTrace = null; + if (self.traces.containsKey(name)) { + myTrace = self.traces.get(name); + } + + if (myTrace == null) { + myTrace = FirebasePerformance.getInstance().newTrace(name); + myTrace.start(); + self.traces.put(name, myTrace); + } + + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private void incrementCounter(final CallbackContext callbackContext, final String name, final String counterNamed) { + final FirebasePlugin self = this; + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + + Trace myTrace = null; + if (self.traces.containsKey(name)) { + myTrace = self.traces.get(name); + } + + if (myTrace != null && myTrace instanceof Trace) { + myTrace.incrementMetric(counterNamed, 1); + callbackContext.success(); + } else { + callbackContext.error("Trace not found"); + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private void stopTrace(final CallbackContext callbackContext, final String name) { + final FirebasePlugin self = this; + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + + Trace myTrace = null; + if (self.traces.containsKey(name)) { + myTrace = self.traces.get(name); + } + + if (myTrace != null && myTrace instanceof Trace) { // + myTrace.stop(); + self.traces.remove(name); + callbackContext.success(); + } else { + callbackContext.error("Trace not found"); + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private void setAnalyticsCollectionEnabled(final CallbackContext callbackContext, final boolean enabled) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + mFirebaseAnalytics.setAnalyticsCollectionEnabled(enabled); + setPreference(ANALYTICS_COLLECTION_ENABLED, enabled); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private void isAnalyticsCollectionEnabled(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + callbackContext.success(getPreference(ANALYTICS_COLLECTION_ENABLED) ? 1 : 0); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private void setPerformanceCollectionEnabled(final CallbackContext callbackContext, final boolean enabled) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + FirebasePerformance.getInstance().setPerformanceCollectionEnabled(enabled); + setPreference(PERFORMANCE_COLLECTION_ENABLED, enabled); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private void isPerformanceCollectionEnabled(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + callbackContext.success(getPreference(PERFORMANCE_COLLECTION_ENABLED) ? 1 : 0); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private void setCrashlyticsCollectionEnabled(final CallbackContext callbackContext, final boolean enabled) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + firebaseCrashlytics.setCrashlyticsCollectionEnabled(enabled); + setPreference(CRASHLYTICS_COLLECTION_ENABLED, enabled); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private void isCrashlyticsCollectionEnabled(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + callbackContext.success(isCrashlyticsEnabled() ? 1 : 0); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + e.printStackTrace(); + } + } + }); + } + + private boolean isCrashlyticsEnabled(){ + return getPreference(CRASHLYTICS_COLLECTION_ENABLED); + } + + public void clearAllNotifications(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + NotificationManager nm = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancelAll(); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void createChannel(final CallbackContext callbackContext, final JSONObject options) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + createChannel(options); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + protected static NotificationChannel createChannel(final JSONObject options) throws JSONException { + NotificationChannel channel = null; + // only call on Android O and above + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String id = options.getString("id"); + Log.i(TAG, "Creating channel id="+id); + + if(channelExists(id)){ + deleteChannel(id); + } + + NotificationManager nm = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE); + String packageName = cordovaActivity.getPackageName(); + + String name = options.optString("name", ""); + Log.d(TAG, "Channel "+id+" - name="+name); + + int importance = options.optInt("importance", NotificationManager.IMPORTANCE_HIGH); + Log.d(TAG, "Channel "+id+" - importance="+importance); + + channel = new NotificationChannel(id, + name, + importance); + + // Description + String description = options.optString("description", ""); + Log.d(TAG, "Channel "+id+" - description="+description); + channel.setDescription(description); + + // Light + boolean light = options.optBoolean("light", true); + Log.d(TAG, "Channel "+id+" - light="+light); + channel.enableLights(light); + + int lightColor = options.optInt("lightColor", -1); + if (lightColor != -1) { + Log.d(TAG, "Channel "+id+" - lightColor="+lightColor); + channel.setLightColor(lightColor); + } + + // Visibility + int visibility = options.optInt("visibility", NotificationCompat.VISIBILITY_PUBLIC); + Log.d(TAG, "Channel "+id+" - visibility="+visibility); + channel.setLockscreenVisibility(visibility); + + // Badge + boolean badge = options.optBoolean("badge", true); + Log.d(TAG, "Channel "+id+" - badge="+badge); + channel.setShowBadge(badge); + + // Sound + String sound = options.optString("sound", "default"); + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); + if ("ringtone".equals(sound)) { + channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), audioAttributes); + Log.d(TAG, "Channel "+id+" - sound=ringtone"); + } else if (sound != null && !sound.contentEquals("default")) { + Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + packageName + "/raw/" + sound); + channel.setSound(soundUri, audioAttributes); + Log.d(TAG, "Channel "+id+" - sound="+sound); + } else if (sound != "false"){ + channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), audioAttributes); + Log.d(TAG, "Channel "+id+" - sound=default"); + }else{ + Log.d(TAG, "Channel "+id+" - sound=none"); + } + + // Vibration: if vibration setting is an array set vibration pattern, else set enable vibration. + JSONArray pattern = options.optJSONArray("vibration"); + if (pattern != null) { + int patternLength = pattern.length(); + long[] patternArray = new long[patternLength]; + for (int i = 0; i < patternLength; i++) { + patternArray[i] = pattern.optLong(i); + } + channel.enableVibration(true); + channel.setVibrationPattern(patternArray); + Log.d(TAG, "Channel "+id+" - vibrate="+pattern); + } else { + boolean vibrate = options.optBoolean("vibration", true); + channel.enableVibration(vibrate); + Log.d(TAG, "Channel "+id+" - vibrate="+vibrate); + } + + // Create channel + nm.createNotificationChannel(channel); + } + return channel; + } + + protected static void createDefaultChannel() throws JSONException { + JSONObject options = new JSONObject(); + options.put("id", defaultChannelId); + options.put("name", defaultChannelName); + createDefaultChannel(options); + } + + protected static void createDefaultChannel(final JSONObject options) throws JSONException { + defaultNotificationChannel = createChannel(options); + } + + public void setDefaultChannel(final CallbackContext callbackContext, final JSONObject options) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + deleteChannel(defaultChannelId); + + String id = options.optString("id", null); + if(id != null){ + defaultChannelId = id; + } + + String name = options.optString("name", null); + if(name != null){ + defaultChannelName = name; + } + createDefaultChannel(options); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public void deleteChannel(final CallbackContext callbackContext, final String channelID) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + deleteChannel(channelID); + callbackContext.success(); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + protected static void deleteChannel(final String channelID){ + // only call on Android O and above + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationManager nm = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE); + nm.deleteNotificationChannel(channelID); + } + } + + public void listChannels(final CallbackContext callbackContext) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + List notificationChannels = listChannels(); + JSONArray channels = new JSONArray(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + for (NotificationChannel notificationChannel : notificationChannels) { + JSONObject channel = new JSONObject(); + channel.put("id", notificationChannel.getId()); + channel.put("name", notificationChannel.getName()); + channels.put(channel); + } + } + callbackContext.success(channels); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + public static List listChannels(){ + List notificationChannels = null; + // only call on Android O and above + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationManager nm = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE); + notificationChannels = nm.getNotificationChannels(); + } + return notificationChannels; + } + + public static boolean channelExists(String channelId){ + boolean exists = false; + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ + List notificationChannels = FirebasePlugin.listChannels(); + if(notificationChannels != null){ + for (NotificationChannel notificationChannel : notificationChannels) { + if(notificationChannel.getId().equals(channelId)){ + exists = true; + } + } + } + } + return exists; + } + + // + // Firestore + // + private void addDocumentToFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String jsonDoc = args.getString(0); + String collection = args.getString(1); + + firestore.collection(collection) + .add(jsonStringToMap(jsonDoc)) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(DocumentReference documentReference) { + callbackContext.success(documentReference.getId()); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + handleExceptionWithContext(e, callbackContext); + } + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void setDocumentInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String documentId = args.getString(0); + String jsonDoc = args.getString(1); + String collection = args.getString(2); + + firestore.collection(collection).document(documentId) + .set(jsonStringToMap(jsonDoc)) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Void aVoid) { + callbackContext.success(); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + handleExceptionWithContext(e, callbackContext); + } + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void updateDocumentInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String documentId = args.getString(0); + String jsonDoc = args.getString(1); + String collection = args.getString(2); + + firestore.collection(collection).document(documentId) + .update(jsonStringToMap(jsonDoc)) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Void aVoid) { + callbackContext.success(); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + handleExceptionWithContext(e, callbackContext); + } + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void deleteDocumentFromFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String documentId = args.getString(0); + String collection = args.getString(1); + + firestore.collection(collection).document(documentId) + .delete() + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Void aVoid) { + callbackContext.success(); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + handleExceptionWithContext(e, callbackContext); + } + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void documentExistsInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String documentId = args.getString(0); + String collection = args.getString(1); + + firestore.collection(collection).document(documentId) + .get() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + DocumentSnapshot document = task.getResult(); + callbackContext.success(document != null && document.getData() != null ? 1 : 0); + } else { + Exception e = task.getException(); + if(e != null){ + handleExceptionWithContext(e, callbackContext); + } + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + handleExceptionWithContext(e, callbackContext); + } + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void fetchDocumentInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String documentId = args.getString(0); + String collection = args.getString(1); + + firestore.collection(collection).document(documentId) + .get() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + DocumentSnapshot document = task.getResult(); + if (document != null && document.getData() != null) { + JSONObject jsonDoc = mapToJsonObject(document.getData()); + callbackContext.success(jsonDoc); + } else { + callbackContext.error("No document found in collection"); + } + } else { + Exception e = task.getException(); + if(e != null){ + handleExceptionWithContext(e, callbackContext); + } + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + handleExceptionWithContext(e, callbackContext); + } + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + private void fetchFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + String collection = args.getString(0); + JSONArray filters = args.getJSONArray(1); + Query query = firestore.collection(collection); + + for(int i = 0; i < filters.length(); i++) { + JSONArray filter = filters.getJSONArray(i); + switch(filter.getString(0)) { + case "where": + if (Objects.equals(filter.getString(2), new String("=="))) { + query = query.whereEqualTo(filter.getString(1), filter.getString(3)); + } + if (Objects.equals(filter.getString(2), new String("<"))) { + query = query.whereLessThan(filter.getString(1), filter.getString(3)); + } + if (Objects.equals(filter.getString(2), new String(">"))) { + query = query.whereGreaterThan(filter.getString(1), filter.getString(3)); + } + if (Objects.equals(filter.getString(2), new String("<="))) { + query = query.whereLessThanOrEqualTo(filter.getString(1), filter.getString(3)); + } + if (Objects.equals(filter.getString(2), new String(">="))) { + query = query.whereGreaterThanOrEqualTo(filter.getString(1), filter.getString(3)); + } + if (Objects.equals(filter.getString(2), new String("array-contains"))) { + query = query.whereArrayContains(filter.getString(1), filter.getString(3)); + } + break; + case "orderBy": + Direction direction = Direction.ASCENDING; + if (Objects.equals(filter.getString(2), new String("desc"))) { + direction = Direction.DESCENDING; + } + query = query.orderBy(filter.getString(1), direction); + break; + case "startAt": + query = query.startAt(filter.getString(1)); + break; + case "endAt": + query = query.endAt(filter.getString(1)); + break; + case "limit": + query = query.limit(filter.getLong(1)); + break; + } + } + + query.get() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + JSONObject jsonDocs = new JSONObject(); + for (QueryDocumentSnapshot document : task.getResult()) { + jsonDocs.put(document.getId(), mapToJsonObject(document.getData())); + } + callbackContext.success(jsonDocs); + } else { + handleExceptionWithContext(task.getException(), callbackContext); + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + }); + } + + + protected static void handleExceptionWithContext(Exception e, CallbackContext context) { + String msg = e.toString(); + Log.e(TAG, msg); + instance.logExceptionToCrashlytics(e); + context.error(msg); + } + + protected static void handleExceptionWithoutContext(Exception e){ + String msg = e.toString(); + Log.e(TAG, msg); + if (instance != null) { + instance.logExceptionToCrashlytics(e); + instance.logErrorToWebview(msg); + } + } + + protected void logErrorToWebview(String msg){ + Log.e(TAG, msg); + executeGlobalJavascript("console.error(\""+TAG+"[native]: "+escapeDoubleQuotes(msg)+"\")"); + } + + private String escapeDoubleQuotes(String string){ + String escapedString = string.replace("\"", "\\\""); + escapedString = escapedString.replace("%22", "\\%22"); + return escapedString; + } + + private void executeGlobalJavascript(final String jsString){ + if(cordovaActivity == null) return; + cordovaActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + webView.loadUrl("javascript:" + jsString); + } + }); + } + + private String saveAuthCredential(AuthCredential authCredential){ + String id = this.generateId(); + this.authCredentials.put(id, authCredential); + return id; + } + + private String saveAuthProvider(OAuthProvider authProvider){ + String id = this.generateId(); + this.authProviders.put(id, authProvider); + return id; + } + + private String generateId(){ + Random r = new Random(); + return Integer.toString(r.nextInt(1000+1)); + } + + private boolean getMetaDataFromManifest(String name) throws Exception{ + return applicationContext.getPackageManager().getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA).metaData.getBoolean(name); + } + + private void setPreference(String name, boolean value){ + SharedPreferences settings = cordovaActivity.getSharedPreferences(SETTINGS_NAME, MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putBoolean(name, value); + editor.apply(); + } + + private boolean getPreference(String name){ + SharedPreferences settings = cordovaActivity.getSharedPreferences(SETTINGS_NAME, MODE_PRIVATE); + return settings.getBoolean(name, false); + } + + private void handleTaskOutcome(@NonNull Task task, CallbackContext callbackContext) { + try { + task.addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful() || task.getException() == null) { + callbackContext.success(); + }else if(task.getException() != null){ + callbackContext.error(task.getException().getMessage()); + }else{ + callbackContext.error("Task failed for unknown reason"); + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + }; + }); + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + + private void handleAuthTaskOutcome(@NonNull Task task, CallbackContext callbackContext) { + try { + if (task.isSuccessful() || task.getException() == null) { + callbackContext.success(); + }else{ + String errMessage = task.getException().getMessage(); + if (task.getException() instanceof FirebaseAuthInvalidCredentialsException) { + errMessage = "Invalid verification code"; + } + callbackContext.error(errMessage); + } + } catch (Exception e) { + handleExceptionWithContext(e, callbackContext); + } + } + + private AuthCredential obtainAuthCredential(JSONObject jsonCredential) throws JSONException { + AuthCredential authCredential = null; + if(jsonCredential.has("verificationId") && jsonCredential.has("code")){ + Log.d(TAG, "Using specified verificationId and code to authenticate"); + authCredential = (AuthCredential) PhoneAuthProvider.getCredential(jsonCredential.getString("verificationId"), jsonCredential.getString("code")); + }else if(jsonCredential.has("id") && FirebasePlugin.instance.authCredentials.containsKey(jsonCredential.getString("id"))){ + Log.d(TAG, "Using native auth credential to authenticate"); + authCredential = FirebasePlugin.instance.authCredentials.get(jsonCredential.getString("id")); + } + return authCredential; + } + + private OAuthProvider obtainAuthProvider(JSONObject jsonCredential) throws JSONException{ + OAuthProvider authProvider = null; + if(jsonCredential.has("id") && FirebasePlugin.instance.authProviders.containsKey(jsonCredential.getString("id"))){ + Log.d(TAG, "Using native auth provider to authenticate"); + authProvider = FirebasePlugin.instance.authProviders.get(jsonCredential.getString("id")); + } + return authProvider; + } + + + private static class AuthResultOnSuccessListener implements OnSuccessListener { + @Override + public void onSuccess(AuthResult authResult) { + Log.d(TAG, "AuthResult:onSuccess:" + authResult); + if(FirebasePlugin.instance.authResultCallbackContext != null){ + FirebasePlugin.instance.authResultCallbackContext.success(); + } + } + } + + private static class AuthResultOnFailureListener implements OnFailureListener { + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "AuthResult:onFailure", e); + if(FirebasePlugin.instance.authResultCallbackContext != null){ + FirebasePlugin.instance.authResultCallbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, e.getMessage())); + } + } + } + + private static class AuthResultOnCompleteListener implements OnCompleteListener { + private final CallbackContext callbackContext; + + public AuthResultOnCompleteListener(CallbackContext callbackContext) { + this.callbackContext = callbackContext; + } + + @Override + public void onComplete(@NonNull Task task) { + FirebasePlugin.instance.handleAuthTaskOutcome(task, callbackContext); + } + } + + private static class AuthStateListener implements FirebaseAuth.AuthStateListener { + @Override + public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + try { + if(!FirebasePlugin.instance.authStateChangeListenerInitialized){ + FirebasePlugin.instance.authStateChangeListenerInitialized = true; + }else{ + FirebaseUser user = firebaseAuth.getCurrentUser(); + FirebasePlugin.instance.executeGlobalJavascript(JS_GLOBAL_NAMESPACE+"_onAuthStateChange("+(user != null ? "true" : "false")+")"); + } + } catch (Exception e) { + handleExceptionWithoutContext(e); + } + } + } + + private Map jsonStringToMap(String jsonString) throws JSONException { + Type type = new TypeToken>(){}.getType(); + return gson.fromJson(jsonString, type); + } + + + private JSONObject mapToJsonObject(Map map) throws JSONException { + String jsonString = gson.toJson(map); + return new JSONObject(jsonString); + } + + private void logMessageToCrashlytics(String message){ + if(isCrashlyticsEnabled()){ + try{ + firebaseCrashlytics.log(message); + }catch (Exception e){ + Log.e(TAG, e.getMessage()); + } + }else{ + Log.e(TAG, "Cannot log message - Crashlytics collection is disabled"); + } + } + + private void logExceptionToCrashlytics(Exception exception){ + if(isCrashlyticsEnabled()){ + try{ + firebaseCrashlytics.recordException(exception); + }catch (Exception e){ + Log.e(TAG, e.getMessage()); + } + }else{ + Log.e(TAG, "Cannot log exception - Crashlytics collection is disabled"); + } + } +} diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessageReceiver.java b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessageReceiver.java new file mode 100644 index 00000000..1bb76d08 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessageReceiver.java @@ -0,0 +1,28 @@ +package org.apache.cordova.firebase; + +import android.os.Bundle; + +import com.google.firebase.messaging.RemoteMessage; + +public abstract class FirebasePluginMessageReceiver { + + public FirebasePluginMessageReceiver() { + FirebasePluginMessageReceiverManager.register(this); + } + + /** + * Concrete subclasses should override this and return true if they handle the received message. + * + * @param remoteMessage + * @return true if the received message was handled by the receiver so should not be handled by FirebasePluginMessagingService.onMessageReceived() + */ + public abstract boolean onMessageReceived(RemoteMessage remoteMessage); + + /** + * Concrete subclasses should override this and return true if they handle the message bundle before it's sent to FirebasePlugin.sendMessage(). + * + * @param bundle + * @return true if the received bundle was handled by the receiver so should not be handled by FirebasePlugin. + */ + public abstract boolean sendMessage(Bundle bundle); +} diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessageReceiverManager.java b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessageReceiverManager.java new file mode 100644 index 00000000..299e5cda --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessageReceiverManager.java @@ -0,0 +1,41 @@ +package org.apache.cordova.firebase; + +import android.os.Bundle; + +import com.google.firebase.messaging.RemoteMessage; + +import java.util.ArrayList; +import java.util.List; + +public class FirebasePluginMessageReceiverManager { + + private static List receivers = new ArrayList(); + + public static void register(FirebasePluginMessageReceiver receiver) { + receivers.add(receiver); + } + + public static boolean onMessageReceived(RemoteMessage remoteMessage) { + boolean handled = false; + for (FirebasePluginMessageReceiver receiver : receivers) { + boolean wasHandled = receiver.onMessageReceived(remoteMessage); + if (wasHandled) { + handled = true; + } + } + + return handled; + } + + public static boolean sendMessage(Bundle bundle) { + boolean handled = false; + for (FirebasePluginMessageReceiver receiver : receivers) { + boolean wasHandled = receiver.sendMessage(bundle); + if (wasHandled) { + handled = true; + } + } + + return handled; + } +} diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessagingService.java b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessagingService.java new file mode 100644 index 00000000..92e6aafb --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/FirebasePluginMessagingService.java @@ -0,0 +1,348 @@ +package org.apache.cordova.firebase; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import androidx.core.app.NotificationCompat; +import android.util.Log; +import android.app.Notification; +import android.text.TextUtils; +import android.content.ContentResolver; +import android.graphics.Color; + +import com.google.firebase.crashlytics.FirebaseCrashlytics; +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +import java.util.Map; +import java.util.Random; + +public class FirebasePluginMessagingService extends FirebaseMessagingService { + + private static final String TAG = "FirebasePlugin"; + + static final String defaultSmallIconName = "notification_icon"; + static final String defaultLargeIconName = "notification_icon_large"; + + + /** + * Called if InstanceID token is updated. This may occur if the security of + * the previous token had been compromised. Note that this is called when the InstanceID token + * is initially generated so this is where you would retrieve the token. + */ + @Override + public void onNewToken(String refreshedToken) { + try{ + super.onNewToken(refreshedToken); + Log.d(TAG, "Refreshed token: " + refreshedToken); + FirebasePlugin.sendToken(refreshedToken); + }catch (Exception e){ + FirebasePlugin.handleExceptionWithoutContext(e); + } + } + + + /** + * Called when message is received. + * Called IF message is a data message (i.e. NOT sent from Firebase console) + * OR if message is a notification message (e.g. sent from Firebase console) AND app is in foreground. + * Notification messages received while app is in background will not be processed by this method; + * they are handled internally by the OS. + * + * @param remoteMessage Object representing the message received from Firebase Cloud Messaging. + */ + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + try{ + // [START_EXCLUDE] + // There are two types of messages data messages and notification messages. Data messages are handled + // here in onMessageReceived whether the app is in the foreground or background. Data messages are the type + // traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app + // is in the foreground. When the app is in the background an automatically generated notification is displayed. + // When the user taps on the notification they are returned to the app. Messages containing both notification + // and data payloads are treated as notification messages. The Firebase console always sends notification + // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options + // [END_EXCLUDE] + + // Pass the message to the receiver manager so any registered receivers can decide to handle it + boolean wasHandled = FirebasePluginMessageReceiverManager.onMessageReceived(remoteMessage); + if (wasHandled) { + Log.d(TAG, "Message was handled by a registered receiver"); + + // Don't process the message in this method. + return; + } + + if(FirebasePlugin.applicationContext == null){ + FirebasePlugin.applicationContext = this.getApplicationContext(); + } + + // TODO(developer): Handle FCM messages here. + // Not getting messages here? See why this may be: https://goo.gl/39bRNJ + String messageType; + String title = null; + String body = null; + String id = null; + String sound = null; + String vibrate = null; + String light = null; + String color = null; + String icon = null; + String channelId = null; + String visibility = null; + String priority = null; + boolean foregroundNotification = false; + + Map data = remoteMessage.getData(); + + if (remoteMessage.getNotification() != null) { + // Notification message payload + Log.i(TAG, "Received message: notification"); + messageType = "notification"; + id = remoteMessage.getMessageId(); + RemoteMessage.Notification notification = remoteMessage.getNotification(); + title = notification.getTitle(); + body = notification.getBody(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + channelId = notification.getChannelId(); + } + sound = notification.getSound(); + color = notification.getColor(); + icon = notification.getIcon(); + }else{ + Log.i(TAG, "Received message: data"); + messageType = "data"; + } + + if (data != null) { + // Data message payload + if(data.containsKey("notification_foreground")){ + foregroundNotification = true; + } + if(data.containsKey("notification_title")) title = data.get("notification_title"); + if(data.containsKey("notification_body")) body = data.get("notification_body"); + if(data.containsKey("notification_android_channel_id")) channelId = data.get("notification_android_channel_id"); + if(data.containsKey("notification_android_id")) id = data.get("notification_android_id"); + if(data.containsKey("notification_android_sound")) sound = data.get("notification_android_sound"); + if(data.containsKey("notification_android_vibrate")) vibrate = data.get("notification_android_vibrate"); + if(data.containsKey("notification_android_light")) light = data.get("notification_android_light"); //String containing hex ARGB color, miliseconds on, miliseconds off, example: '#FFFF00FF,1000,3000' + if(data.containsKey("notification_android_color")) color = data.get("notification_android_color"); + if(data.containsKey("notification_android_icon")) icon = data.get("notification_android_icon"); + if(data.containsKey("notification_android_visibility")) visibility = data.get("notification_android_visibility"); + if(data.containsKey("notification_android_priority")) priority = data.get("notification_android_priority"); + } + + if (TextUtils.isEmpty(id)) { + Random rand = new Random(); + int n = rand.nextInt(50) + 1; + id = Integer.toString(n); + } + + Log.d(TAG, "From: " + remoteMessage.getFrom()); + Log.d(TAG, "Id: " + id); + Log.d(TAG, "Title: " + title); + Log.d(TAG, "Body: " + body); + Log.d(TAG, "Sound: " + sound); + Log.d(TAG, "Vibrate: " + vibrate); + Log.d(TAG, "Light: " + light); + Log.d(TAG, "Color: " + color); + Log.d(TAG, "Icon: " + icon); + Log.d(TAG, "Channel Id: " + channelId); + Log.d(TAG, "Visibility: " + visibility); + Log.d(TAG, "Priority: " + priority); + + + if (!TextUtils.isEmpty(body) || !TextUtils.isEmpty(title) || (data != null && !data.isEmpty())) { + boolean showNotification = (FirebasePlugin.inBackground() || !FirebasePlugin.hasNotificationsCallback() || foregroundNotification) && (!TextUtils.isEmpty(body) || !TextUtils.isEmpty(title)); + sendMessage(remoteMessage, data, messageType, id, title, body, showNotification, sound, vibrate, light, color, icon, channelId, priority, visibility); + } + }catch (Exception e){ + FirebasePlugin.handleExceptionWithoutContext(e); + } + } + + private void sendMessage(RemoteMessage remoteMessage, Map data, String messageType, String id, String title, String body, boolean showNotification, String sound, String vibrate, String light, String color, String icon, String channelId, String priority, String visibility) { + Log.d(TAG, "sendMessage(): messageType="+messageType+"; showNotification="+showNotification+"; id="+id+"; title="+title+"; body="+body+"; sound="+sound+"; vibrate="+vibrate+"; light="+light+"; color="+color+"; icon="+icon+"; channel="+channelId+"; data="+data.toString()); + Bundle bundle = new Bundle(); + for (String key : data.keySet()) { + bundle.putString(key, data.get(key)); + } + bundle.putString("messageType", messageType); + this.putKVInBundle("id", id, bundle); + this.putKVInBundle("title", title, bundle); + this.putKVInBundle("body", body, bundle); + this.putKVInBundle("sound", sound, bundle); + this.putKVInBundle("vibrate", vibrate, bundle); + this.putKVInBundle("light", light, bundle); + this.putKVInBundle("color", color, bundle); + this.putKVInBundle("icon", icon, bundle); + this.putKVInBundle("channel_id", channelId, bundle); + this.putKVInBundle("priority", priority, bundle); + this.putKVInBundle("visibility", visibility, bundle); + this.putKVInBundle("show_notification", String.valueOf(showNotification), bundle); + this.putKVInBundle("from", remoteMessage.getFrom(), bundle); + this.putKVInBundle("collapse_key", remoteMessage.getCollapseKey(), bundle); + this.putKVInBundle("sent_time", String.valueOf(remoteMessage.getSentTime()), bundle); + this.putKVInBundle("ttl", String.valueOf(remoteMessage.getTtl()), bundle); + + if (showNotification) { + Intent intent = new Intent(this, OnNotificationOpenReceiver.class); + intent.putExtras(bundle); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT); + + // Channel + if(channelId == null || !FirebasePlugin.channelExists(channelId)){ + channelId = FirebasePlugin.defaultChannelId; + } + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ + Log.d(TAG, "Channel ID: "+channelId); + } + + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId); + notificationBuilder + .setContentTitle(title) + .setContentText(body) + .setStyle(new NotificationCompat.BigTextStyle().bigText(body)) + .setAutoCancel(true) + .setContentIntent(pendingIntent); + + // On Android O+ the sound/lights/vibration are determined by the channel ID + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O){ + // Sound + if (sound == null) { + Log.d(TAG, "Sound: none"); + }else if (sound.equals("default")) { + notificationBuilder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); + Log.d(TAG, "Sound: default"); + }else{ + Uri soundPath = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getPackageName() + "/raw/" + sound); + Log.d(TAG, "Sound: custom=" + sound+"; path="+soundPath.toString()); + notificationBuilder.setSound(soundPath); + } + + // Light + if (light != null) { + try { + String[] lightsComponents = color.replaceAll("\\s", "").split(","); + if (lightsComponents.length == 3) { + int lightArgb = Color.parseColor(lightsComponents[0]); + int lightOnMs = Integer.parseInt(lightsComponents[1]); + int lightOffMs = Integer.parseInt(lightsComponents[2]); + notificationBuilder.setLights(lightArgb, lightOnMs, lightOffMs); + Log.d(TAG, "Lights: color="+lightsComponents[0]+"; on(ms)="+lightsComponents[2]+"; off(ms)="+lightsComponents[3]); + } + + } catch (Exception e) {} + } + + // Vibrate + if (vibrate != null){ + try { + String[] sVibrations = vibrate.replaceAll("\\s", "").split(","); + long[] lVibrations = new long[sVibrations.length]; + int i=0; + for(String sVibration: sVibrations){ + lVibrations[i] = Integer.parseInt(sVibration.trim()); + i++; + } + notificationBuilder.setVibrate(lVibrations); + Log.d(TAG, "Vibrate: "+vibrate); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } + } + } + + + // Icon + int defaultSmallIconResID = getResources().getIdentifier(defaultSmallIconName, "drawable", getPackageName()); + int customSmallIconResID = 0; + if(icon != null){ + customSmallIconResID = getResources().getIdentifier(icon, "drawable", getPackageName()); + } + + if (customSmallIconResID != 0) { + notificationBuilder.setSmallIcon(customSmallIconResID); + Log.d(TAG, "Small icon: custom="+icon); + }else if (defaultSmallIconResID != 0) { + Log.d(TAG, "Small icon: default="+defaultSmallIconName); + notificationBuilder.setSmallIcon(defaultSmallIconResID); + } else { + Log.d(TAG, "Small icon: application"); + notificationBuilder.setSmallIcon(getApplicationInfo().icon); + } + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + int defaultLargeIconResID = getResources().getIdentifier(defaultLargeIconName, "drawable", getPackageName()); + int customLargeIconResID = 0; + if(icon != null){ + customLargeIconResID = getResources().getIdentifier(icon+"_large", "drawable", getPackageName()); + } + + int largeIconResID; + if (customLargeIconResID != 0 || defaultLargeIconResID != 0) { + if (customLargeIconResID != 0) { + largeIconResID = customLargeIconResID; + Log.d(TAG, "Large icon: custom="+icon); + }else{ + Log.d(TAG, "Large icon: default="+defaultLargeIconName); + largeIconResID = defaultLargeIconResID; + } + notificationBuilder.setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(), largeIconResID)); + } + } + + // Color + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + int defaultColor = getResources().getColor(getResources().getIdentifier("accent", "color", getPackageName()), null); + if(color != null){ + notificationBuilder.setColor(Color.parseColor(color)); + Log.d(TAG, "Color: custom="+color); + }else{ + Log.d(TAG, "Color: default"); + notificationBuilder.setColor(defaultColor); + } + } + + // Visibility + int iVisibility = NotificationCompat.VISIBILITY_PUBLIC; + if(visibility != null){ + iVisibility = Integer.parseInt(visibility); + } + Log.d(TAG, "Visibility: " + iVisibility); + notificationBuilder.setVisibility(iVisibility); + + // Priority + int iPriority = NotificationCompat.PRIORITY_MAX; + if(priority != null){ + iPriority = Integer.parseInt(priority); + } + Log.d(TAG, "Priority: " + iPriority); + notificationBuilder.setPriority(iPriority); + + + // Build notification + Notification notification = notificationBuilder.build(); + + // Display notification + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + Log.d(TAG, "show notification: "+notification.toString()); + notificationManager.notify(id.hashCode(), notification); + } + // Send to plugin + FirebasePlugin.sendMessage(bundle, this.getApplicationContext()); + } + + private void putKVInBundle(String k, String v, Bundle b){ + if(v != null && !b.containsKey(k)){ + b.putString(k, v); + } + } +} diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/JavaScriptException.java b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/JavaScriptException.java new file mode 100644 index 00000000..3d423fa3 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/JavaScriptException.java @@ -0,0 +1,45 @@ +package org.apache.cordova.firebase; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Exception class to log Javascript based exceptions with stacktrace. + * + * Picked from https://github.com/wizpanda/cordova-plugin-firebase-lib/pull/8/files + * + * @author https://github.com/sagrawal31/ + */ +public class JavaScriptException extends Exception { + + public JavaScriptException(String message) { + super(message); + } + + public JavaScriptException(String message, JSONArray stackTrace) throws JSONException { + super(message); + this.handleStacktrace(stackTrace); + } + + private void handleStacktrace(JSONArray stackTrace) throws JSONException { + if (stackTrace == null) { + return; + } + + StackTraceElement[] trace = new StackTraceElement[stackTrace.length()]; + + for (int i = 0; i < stackTrace.length(); i++) { + JSONObject elem = stackTrace.getJSONObject(i); + + trace[i] = new StackTraceElement( + "undefined", + elem.optString("functionName", "undefined"), + elem.optString("fileName", "undefined"), + elem.optInt("lineNumber", -1) + ); + } + + this.setStackTrace(trace); + } +} diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/OnNotificationOpenReceiver.java b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/OnNotificationOpenReceiver.java new file mode 100644 index 00000000..818382ef --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/OnNotificationOpenReceiver.java @@ -0,0 +1,36 @@ +package org.apache.cordova.firebase; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; + +public class OnNotificationOpenReceiver extends BroadcastReceiver { + + // Called on tapping foreground notification + @Override + public void onReceive(Context context, Intent intent) { + try{ + PackageManager pm = context.getPackageManager(); + + Intent launchIntent = pm.getLaunchIntentForPackage(context.getPackageName()); + launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + + Bundle data = intent.getExtras(); + if(!data.containsKey("messageType")) data.putString("messageType", "notification"); + data.putString("tap", FirebasePlugin.inBackground() ? "background" : "foreground"); + + Log.d(FirebasePlugin.TAG, "OnNotificationOpenReceiver.onReceive(): "+data.toString()); + + FirebasePlugin.sendMessage(data, context); + + launchIntent.putExtras(data); + context.startActivity(launchIntent); + }catch (Exception e){ + FirebasePlugin.handleExceptionWithoutContext(e); + } + } +} diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/build.gradle b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/build.gradle new file mode 100644 index 00000000..89c743f1 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/build.gradle @@ -0,0 +1,48 @@ +buildscript { + repositories { + google() + mavenCentral() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.google.gms:google-services:4.3.3' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' + } +} +repositories { + mavenCentral() + maven { + url "https://maven.google.com" + } +} + +apply plugin: com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsPlugin +android { + buildTypes { + debug { + firebaseCrashlytics { + mappingFileUploadEnabled false + } + } + release { + firebaseCrashlytics { + nativeSymbolUploadEnabled true + unstrippedNativeLibsDir "obj/local" + strippedNativeLibsDir "build/intermediates/jniLibs/release" + } + } + } +} + +cdvPluginPostBuildExtras.add({ + rootProject.subprojects { + if (name == "app") { + if (!plugins.hasPlugin('com.google.gms.google-services')) { + apply plugin: com.google.gms.googleservices.GoogleServicesPlugin + } + } + } +}) + + diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/colors.xml b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/colors.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/colors.xml @@ -0,0 +1,3 @@ + + + diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/android/cordova-plugin-firebase-strings.xml b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/cordova-plugin-firebase-strings.xml new file mode 100644 index 00000000..1f1d32cc --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/android/cordova-plugin-firebase-strings.xml @@ -0,0 +1,5 @@ + + + fcm_default_channel + Default + \ No newline at end of file diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/AppDelegate+FirebasePlugin.h b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/AppDelegate+FirebasePlugin.h new file mode 100644 index 00000000..461b17e4 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/AppDelegate+FirebasePlugin.h @@ -0,0 +1,11 @@ +#import "AppDelegate.h" +#import + +@import UserNotifications; +@import AuthenticationServices; + +@interface AppDelegate (FirebasePlugin) ++ (AppDelegate *) instance; +@property (nonatomic, strong) NSNumber * _Nonnull applicationInBackground; +@property (NS_NONATOMIC_IOSONLY, nullable, weak) id delegate; +@end diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/AppDelegate+FirebasePlugin.m b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/AppDelegate+FirebasePlugin.m new file mode 100644 index 00000000..a14c6569 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/AppDelegate+FirebasePlugin.m @@ -0,0 +1,557 @@ +#import "AppDelegate+FirebasePlugin.h" +#import "FirebasePlugin.h" +#import "Firebase.h" +#import + + +@import UserNotifications; +@import FirebaseFirestore; + +// Implement UNUserNotificationCenterDelegate to receive display notification via APNS for devices running iOS 10 and above. +// Implement FIRMessagingDelegate to receive data message via FCM for devices running iOS 10 and above. +@interface AppDelegate () +@end + +#define kApplicationInBackgroundKey @"applicationInBackground" + +@implementation AppDelegate (FirebasePlugin) + +static AppDelegate* instance; + ++ (AppDelegate*) instance { + return instance; +} + +static NSDictionary* mutableUserInfo; +static FIRAuthStateDidChangeListenerHandle authStateChangeListener; +static bool authStateChangeListenerInitialized = false; +static bool shouldEstablishDirectChannel = false; + ++ (void)load { + Method original = class_getInstanceMethod(self, @selector(application:didFinishLaunchingWithOptions:)); + Method swizzled = class_getInstanceMethod(self, @selector(application:swizzledDidFinishLaunchingWithOptions:)); + method_exchangeImplementations(original, swizzled); +} + +- (void)setApplicationInBackground:(NSNumber *)applicationInBackground { + objc_setAssociatedObject(self, kApplicationInBackgroundKey, applicationInBackground, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSNumber *)applicationInBackground { + return objc_getAssociatedObject(self, kApplicationInBackgroundKey); +} + +- (BOOL)application:(UIApplication *)application swizzledDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [self application:application swizzledDidFinishLaunchingWithOptions:launchOptions]; + + @try{ + instance = self; + + bool isFirebaseInitializedWithPlist = false; + if(![FIRApp defaultApp]) { + // get GoogleService-Info.plist file path + NSString *filePath = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; + + // if file is successfully found, use it + if(filePath){ + [FirebasePlugin.firebasePlugin _logMessage:@"GoogleService-Info.plist found, setup: [FIRApp configureWithOptions]"]; + // create firebase configure options passing .plist as content + FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath]; + + // configure FIRApp with options + [FIRApp configureWithOptions:options]; + + isFirebaseInitializedWithPlist = true; + }else{ + // no .plist found, try default App + [FirebasePlugin.firebasePlugin _logError:@"GoogleService-Info.plist NOT FOUND, setup: [FIRApp defaultApp]"]; + [FIRApp configure]; + } + }else{ + // Firebase SDK has already been initialised: + // Assume that another call (probably from another plugin) did so with the plist + isFirebaseInitializedWithPlist = true; + } + + + + shouldEstablishDirectChannel = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"shouldEstablishDirectChannel"] boolValue]; + + // Set FCM messaging delegate + [FIRMessaging messaging].delegate = self; + [FIRMessaging messaging].shouldEstablishDirectChannel = shouldEstablishDirectChannel; + + // Setup Firestore + [FirebasePlugin setFirestore:[FIRFirestore firestore]]; + + // Setup Google SignIn + [GIDSignIn sharedInstance].clientID = [FIRApp defaultApp].options.clientID; + [GIDSignIn sharedInstance].delegate = self; + + authStateChangeListener = [[FIRAuth auth] addAuthStateDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) { + @try { + if(!authStateChangeListenerInitialized){ + authStateChangeListenerInitialized = true; + }else{ + [FirebasePlugin.firebasePlugin executeGlobalJavascript:[NSString stringWithFormat:@"FirebasePlugin._onAuthStateChange(%@)", (user != nil ? @"true": @"false")]]; + } + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } + }]; + + // Set NSNotificationCenter observer + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tokenRefreshNotification:) + name:kFIRInstanceIDTokenRefreshNotification object:nil]; + + self.applicationInBackground = @(YES); + + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } + + return YES; +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + self.applicationInBackground = @(NO); + [FIRMessaging messaging].shouldEstablishDirectChannel = shouldEstablishDirectChannel; + [FirebasePlugin.firebasePlugin _logMessage:[NSString stringWithFormat:@"Enter foreground: FCM direct channel = %@", shouldEstablishDirectChannel ? @"true" : @"false"]]; +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + self.applicationInBackground = @(YES); + [FIRMessaging messaging].shouldEstablishDirectChannel = false; + [FirebasePlugin.firebasePlugin _logMessage:@"Enter background: FCM direct channel = false"]; +} + +# pragma mark - Google SignIn +- (void)signIn:(GIDSignIn *)signIn +didSignInForUser:(GIDGoogleUser *)user + withError:(NSError *)error { + @try{ + CDVPluginResult* pluginResult; + if (error == nil) { + GIDAuthentication *authentication = user.authentication; + FIRAuthCredential *credential = + [FIRGoogleAuthProvider credentialWithIDToken:authentication.idToken + accessToken:authentication.accessToken]; + + int key = [[FirebasePlugin firebasePlugin] saveAuthCredential:credential]; + NSMutableDictionary* result = [[NSMutableDictionary alloc] init]; + [result setValue:@"true" forKey:@"instantVerification"]; + [result setValue:[NSNumber numberWithInt:key] forKey:@"id"]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description]; + } + if ([FirebasePlugin firebasePlugin].googleSignInCallbackId != nil) { + [[FirebasePlugin firebasePlugin].commandDelegate sendPluginResult:pluginResult callbackId:[FirebasePlugin firebasePlugin].googleSignInCallbackId]; + } + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } +} + +- (void)signIn:(GIDSignIn *)signIn +didDisconnectWithUser:(GIDGoogleUser *)user + withError:(NSError *)error { + NSString* msg = @"Google SignIn delegate: didDisconnectWithUser"; + if(error != nil){ + [FirebasePlugin.firebasePlugin _logError:[NSString stringWithFormat:@"%@: %@", msg, error]]; + }else{ + [FirebasePlugin.firebasePlugin _logMessage:msg]; + } +} + +# pragma mark - FIRMessagingDelegate +- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { + [FirebasePlugin.firebasePlugin _logMessage:[NSString stringWithFormat:@"didReceiveRegistrationToken: %@", fcmToken]]; + @try{ + [FirebasePlugin.firebasePlugin sendToken:fcmToken]; + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } +} + +- (void)tokenRefreshNotification:(NSNotification *)notification { + // Note that this callback will be fired everytime a new token is generated, including the first + // time. So if you need to retrieve the token as soon as it is available this is where that + // should be done. + @try{ + [[FIRInstanceID instanceID] instanceIDWithHandler:^(FIRInstanceIDResult * _Nullable result, + NSError * _Nullable error) { + @try{ + if (error == nil) { + NSString *refreshedToken = result.token; + [FirebasePlugin.firebasePlugin _logMessage:[NSString stringWithFormat:@"tokenRefreshNotification: %@", refreshedToken]]; + [FirebasePlugin.firebasePlugin sendToken:refreshedToken]; + }else{ + [FirebasePlugin.firebasePlugin _logError:[NSString stringWithFormat:@"tokenRefreshNotification: %@", error.description]]; + } + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } + }]; + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } +} + +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + [FIRMessaging messaging].APNSToken = deviceToken; + [FirebasePlugin.firebasePlugin _logMessage:[NSString stringWithFormat:@"didRegisterForRemoteNotificationsWithDeviceToken: %@", deviceToken]]; + [FirebasePlugin.firebasePlugin sendApnsToken:[FirebasePlugin.firebasePlugin hexadecimalStringFromData:deviceToken]]; + + // Set UNUserNotificationCenter delegate + [UNUserNotificationCenter currentNotificationCenter].delegate = self; +} + +//Tells the app that a remote notification arrived that indicates there is data to be fetched. +// Called when a message arrives in the foreground and remote notifications permission has been granted +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + + @try{ + [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; + mutableUserInfo = [userInfo mutableCopy]; + NSDictionary* aps = [mutableUserInfo objectForKey:@"aps"]; + bool isContentAvailable = false; + if([aps objectForKey:@"alert"] != nil){ + isContentAvailable = [[aps objectForKey:@"content-available"] isEqualToNumber:[NSNumber numberWithInt:1]]; + [mutableUserInfo setValue:@"notification" forKey:@"messageType"]; + NSString* tap; + if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]] && !isContentAvailable){ + tap = @"background"; + } + [mutableUserInfo setValue:tap forKey:@"tap"]; + }else{ + [mutableUserInfo setValue:@"data" forKey:@"messageType"]; + } + + [FirebasePlugin.firebasePlugin _logMessage:[NSString stringWithFormat:@"didReceiveRemoteNotification: %@", mutableUserInfo]]; + + completionHandler(UIBackgroundFetchResultNewData); + if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]] && isContentAvailable){ + [FirebasePlugin.firebasePlugin _logError:@"didReceiveRemoteNotification: omitting foreground notification as content-available:1 so system notification will be shown"]; + }else{ + [self processMessageForForegroundNotification:mutableUserInfo]; + } + if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]] || !isContentAvailable){ + [FirebasePlugin.firebasePlugin sendNotification:mutableUserInfo]; + } + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } +} + +// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground. +// Called when a data message is arrives in the foreground and remote notifications permission has been NOT been granted +- (void)messaging:(FIRMessaging *)messaging didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage { + @try{ + [FirebasePlugin.firebasePlugin _logMessage:[NSString stringWithFormat:@"didReceiveMessage: %@", remoteMessage.appData]]; + + NSDictionary* appData = [remoteMessage.appData mutableCopy]; + [appData setValue:@"data" forKey:@"messageType"]; + [self processMessageForForegroundNotification:appData]; + + // This will allow us to handle FCM data-only push messages even if the permission for push + // notifications is yet missing. This will only work when the app is in the foreground. + [FirebasePlugin.firebasePlugin sendNotification:appData]; + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } +} + +// Scans a message for keys which indicate a notification should be shown. +// If found, extracts relevant keys and uses then to display a local notification +-(void)processMessageForForegroundNotification:(NSDictionary*)messageData { + bool showForegroundNotification = [messageData objectForKey:@"notification_foreground"]; + if(!showForegroundNotification){ + return; + } + + NSString* title = nil; + NSString* body = nil; + NSString* sound = nil; + NSNumber* badge = nil; + + // Extract APNS notification keys + NSDictionary* aps = [messageData objectForKey:@"aps"]; + if([aps objectForKey:@"alert"] != nil){ + NSDictionary* alert = [aps objectForKey:@"alert"]; + if([alert objectForKey:@"title"] != nil){ + title = [alert objectForKey:@"title"]; + } + if([alert objectForKey:@"body"] != nil){ + body = [alert objectForKey:@"body"]; + } + if([aps objectForKey:@"sound"] != nil){ + sound = [aps objectForKey:@"sound"]; + } + if([aps objectForKey:@"badge"] != nil){ + badge = [aps objectForKey:@"badge"]; + } + } + + // Extract data notification keys + if([messageData objectForKey:@"notification_title"] != nil){ + title = [messageData objectForKey:@"notification_title"]; + } + if([messageData objectForKey:@"notification_body"] != nil){ + body = [messageData objectForKey:@"notification_body"]; + } + if([messageData objectForKey:@"notification_ios_sound"] != nil){ + sound = [messageData objectForKey:@"notification_ios_sound"]; + } + if([messageData objectForKey:@"notification_ios_badge"] != nil){ + badge = [messageData objectForKey:@"notification_ios_badge"]; + } + + if(title == nil || body == nil){ + return; + } + + [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { + @try{ + if (settings.alertSetting == UNNotificationSettingEnabled) { + UNMutableNotificationContent *objNotificationContent = [[UNMutableNotificationContent alloc] init]; + objNotificationContent.title = [NSString localizedUserNotificationStringForKey:title arguments:nil]; + objNotificationContent.body = [NSString localizedUserNotificationStringForKey:body arguments:nil]; + + NSDictionary* alert = [[NSDictionary alloc] initWithObjectsAndKeys: + title, @"title", + body, @"body" + , nil]; + NSMutableDictionary* aps = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + alert, @"alert", + nil]; + + if(![sound isKindOfClass:[NSString class]] || [sound isEqualToString:@"default"]){ + objNotificationContent.sound = [UNNotificationSound defaultSound]; + [aps setValue:sound forKey:@"sound"]; + }else if(sound != nil){ + objNotificationContent.sound = [UNNotificationSound soundNamed:sound]; + [aps setValue:sound forKey:@"sound"]; + } + + if(badge != nil){ + [aps setValue:badge forKey:@"badge"]; + } + + NSString* messageType = @"data"; + if([mutableUserInfo objectForKey:@"messageType"] != nil){ + messageType = [mutableUserInfo objectForKey:@"messageType"]; + } + + NSDictionary* userInfo = [[NSDictionary alloc] initWithObjectsAndKeys: + @"true", @"notification_foreground", + messageType, @"messageType", + aps, @"aps" + , nil]; + + objNotificationContent.userInfo = userInfo; + + UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.1f repeats:NO]; + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"local_notification" content:objNotificationContent trigger:trigger]; + [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { + if (!error) { + [FirebasePlugin.firebasePlugin _logMessage:@"Local Notification succeeded"]; + } else { + [FirebasePlugin.firebasePlugin _logError:[NSString stringWithFormat:@"Local Notification failed: %@", error.description]]; + } + }]; + }else{ + [FirebasePlugin.firebasePlugin _logError:@"processMessageForForegroundNotification: cannot show notification as permission denied"]; + } + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } + }]; +} + +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + [FirebasePlugin.firebasePlugin _logError:[NSString stringWithFormat:@"didFailToRegisterForRemoteNotificationsWithError: %@", error.description]]; +} + +// Asks the delegate how to handle a notification that arrived while the app was running in the foreground +// Called when an APS notification arrives when app is in foreground +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { + + @try{ + + if (![notification.request.trigger isKindOfClass:UNPushNotificationTrigger.class] && ![notification.request.trigger isKindOfClass:UNTimeIntervalNotificationTrigger.class]){ + [FirebasePlugin.firebasePlugin _logError:@"willPresentNotification: aborting as not a supported UNNotificationTrigger"]; + return; + } + + [[FIRMessaging messaging] appDidReceiveMessage:notification.request.content.userInfo]; + + mutableUserInfo = [notification.request.content.userInfo mutableCopy]; + + NSString* messageType = [mutableUserInfo objectForKey:@"messageType"]; + if(![messageType isEqualToString:@"data"]){ + [mutableUserInfo setValue:@"notification" forKey:@"messageType"]; + } + + // Print full message. + [FirebasePlugin.firebasePlugin _logMessage:[NSString stringWithFormat:@"willPresentNotification: %@", mutableUserInfo]]; + + + NSDictionary* aps = [mutableUserInfo objectForKey:@"aps"]; + bool isContentAvailable = [[aps objectForKey:@"content-available"] isEqualToNumber:[NSNumber numberWithInt:1]]; + if(isContentAvailable){ + [FirebasePlugin.firebasePlugin _logError:@"willPresentNotification: aborting as content-available:1 so system notification will be shown"]; + return; + } + + bool showForegroundNotification = [mutableUserInfo objectForKey:@"notification_foreground"]; + bool hasAlert = [aps objectForKey:@"alert"] != nil; + bool hasBadge = [aps objectForKey:@"badge"] != nil; + bool hasSound = [aps objectForKey:@"sound"] != nil; + + if(showForegroundNotification){ + [FirebasePlugin.firebasePlugin _logMessage:[NSString stringWithFormat:@"willPresentNotification: foreground notification alert=%@, badge=%@, sound=%@", hasAlert ? @"YES" : @"NO", hasBadge ? @"YES" : @"NO", hasSound ? @"YES" : @"NO"]]; + if(hasAlert && hasBadge && hasSound){ + completionHandler(UNNotificationPresentationOptionAlert + UNNotificationPresentationOptionBadge + UNNotificationPresentationOptionSound); + }else if(hasAlert && hasBadge){ + completionHandler(UNNotificationPresentationOptionAlert + UNNotificationPresentationOptionBadge); + }else if(hasAlert && hasSound){ + completionHandler(UNNotificationPresentationOptionAlert + UNNotificationPresentationOptionSound); + }else if(hasBadge && hasSound){ + completionHandler(UNNotificationPresentationOptionBadge + UNNotificationPresentationOptionSound); + }else if(hasAlert){ + completionHandler(UNNotificationPresentationOptionAlert); + }else if(hasBadge){ + completionHandler(UNNotificationPresentationOptionBadge); + }else if(hasSound){ + completionHandler(UNNotificationPresentationOptionSound); + } + }else{ + [FirebasePlugin.firebasePlugin _logMessage:@"willPresentNotification: foreground notification not set"]; + } + + if(![messageType isEqualToString:@"data"]){ + [FirebasePlugin.firebasePlugin sendNotification:mutableUserInfo]; + } + + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } +} + +// Asks the delegate to process the user's response to a delivered notification. +// Called when user taps on system notification +- (void) userNotificationCenter:(UNUserNotificationCenter *)center + didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler +{ + @try{ + + if (![response.notification.request.trigger isKindOfClass:UNPushNotificationTrigger.class] && ![response.notification.request.trigger isKindOfClass:UNTimeIntervalNotificationTrigger.class]){ + [FirebasePlugin.firebasePlugin _logMessage:@"didReceiveNotificationResponse: aborting as not a supported UNNotificationTrigger"]; + return; + } + + [[FIRMessaging messaging] appDidReceiveMessage:response.notification.request.content.userInfo]; + + mutableUserInfo = [response.notification.request.content.userInfo mutableCopy]; + + NSString* tap; + if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]]){ + tap = @"background"; + }else{ + tap = @"foreground"; + + } + [mutableUserInfo setValue:tap forKey:@"tap"]; + if([mutableUserInfo objectForKey:@"messageType"] == nil){ + [mutableUserInfo setValue:@"notification" forKey:@"messageType"]; + } + + // Dynamic Actions + if (response.actionIdentifier && ![response.actionIdentifier isEqual:UNNotificationDefaultActionIdentifier]) { + [mutableUserInfo setValue:response.actionIdentifier forKey:@"action"]; + } + + // Print full message. + [FirebasePlugin.firebasePlugin _logInfo:[NSString stringWithFormat:@"didReceiveNotificationResponse: %@", mutableUserInfo]]; + + [FirebasePlugin.firebasePlugin sendNotification:mutableUserInfo]; + + completionHandler(); + + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } +} + +// Receive data message on iOS 10 devices. +- (void)applicationReceivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { + // Print full message + [FirebasePlugin.firebasePlugin _logInfo:[NSString stringWithFormat:@"applicationReceivedRemoteMessage: %@", [remoteMessage appData]]]; +} + +// Apple Sign In +- (void)authorizationController:(ASAuthorizationController *)controller + didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) { + @try{ + CDVPluginResult* pluginResult; + NSString* errorMessage = nil; + FIROAuthCredential *credential; + + if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) { + ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential; + NSString *rawNonce = [FirebasePlugin appleSignInNonce]; + if(rawNonce == nil){ + errorMessage = @"Invalid state: A login callback was received, but no login request was sent."; + }else if (appleIDCredential.identityToken == nil) { + errorMessage = @"Unable to fetch identity token."; + }else{ + NSString *idToken = [[NSString alloc] initWithData:appleIDCredential.identityToken + encoding:NSUTF8StringEncoding]; + if (idToken == nil) { + errorMessage = [NSString stringWithFormat:@"Unable to serialize id token from data: %@", appleIDCredential.identityToken]; + }else{ + // Initialize a Firebase credential. + credential = [FIROAuthProvider credentialWithProviderID:@"apple.com" + IDToken:idToken + rawNonce:rawNonce]; + + int key = [[FirebasePlugin firebasePlugin] saveAuthCredential:credential]; + NSMutableDictionary* result = [[NSMutableDictionary alloc] init]; + [result setValue:@"true" forKey:@"instantVerification"]; + [result setValue:[NSNumber numberWithInt:key] forKey:@"id"]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; + } + } + if(errorMessage != nil){ + [FirebasePlugin.firebasePlugin _logError:errorMessage]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; + } + if ([FirebasePlugin firebasePlugin].appleSignInCallbackId != nil) { + [[FirebasePlugin firebasePlugin].commandDelegate sendPluginResult:pluginResult callbackId:[FirebasePlugin firebasePlugin].appleSignInCallbackId]; + } + } + }@catch (NSException *exception) { + [FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception]; + } +} + +- (void)authorizationController:(ASAuthorizationController *)controller + didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) { + NSString* errorMessage = [NSString stringWithFormat:@"Sign in with Apple errored: %@", error]; + [FirebasePlugin.firebasePlugin _logError:errorMessage]; + if ([FirebasePlugin firebasePlugin].appleSignInCallbackId != nil) { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; + [[FirebasePlugin firebasePlugin].commandDelegate sendPluginResult:pluginResult callbackId:[FirebasePlugin firebasePlugin].appleSignInCallbackId]; + } +} + +- (nonnull ASPresentationAnchor)presentationAnchorForAuthorizationController:(nonnull ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){ + return self.viewController.view.window; +} + +@end diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePlugin.h b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePlugin.h new file mode 100644 index 00000000..2a65108d --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePlugin.h @@ -0,0 +1,119 @@ +#import +#import "AppDelegate.h" +#import "Firebase.h" +@import FirebaseFirestore; + +@interface FirebasePlugin : CDVPlugin + +- (void)setAutoInitEnabled:(CDVInvokedUrlCommand*)command; +- (void)isAutoInitEnabled:(CDVInvokedUrlCommand*)command; + +// Authentication +- (void)verifyPhoneNumber:(CDVInvokedUrlCommand*)command; +- (void)createUserWithEmailAndPassword:(CDVInvokedUrlCommand*)command; +- (void)signInUserWithEmailAndPassword:(CDVInvokedUrlCommand*)command; +- (void)signInUserWithCustomToken:(CDVInvokedUrlCommand*)command; +- (void)signInUserAnonymously:(CDVInvokedUrlCommand*)command; +- (void)authenticateUserWithGoogle:(CDVInvokedUrlCommand*)command; +- (void)authenticateUserWithApple:(CDVInvokedUrlCommand*)command; +- (void)signInWithCredential:(CDVInvokedUrlCommand*)command; +- (void)linkUserWithCredential:(CDVInvokedUrlCommand*)command; +- (void)reauthenticateWithCredential:(CDVInvokedUrlCommand*)command; +- (void)isUserSignedIn:(CDVInvokedUrlCommand*)command; +- (void)signOutUser:(CDVInvokedUrlCommand*)command; +- (void)getCurrentUser:(CDVInvokedUrlCommand*)command; +- (void)reloadCurrentUser:(CDVInvokedUrlCommand*)command; +- (void)updateUserProfile:(CDVInvokedUrlCommand*)command; +- (void)updateUserEmail:(CDVInvokedUrlCommand*)command; +- (void)sendUserEmailVerification:(CDVInvokedUrlCommand*)command; +- (void)updateUserPassword:(CDVInvokedUrlCommand*)command; +- (void)sendUserPasswordResetEmail:(CDVInvokedUrlCommand*)command; +- (void)deleteUser:(CDVInvokedUrlCommand*)command; + +// Remote notifications +- (void)getId:(CDVInvokedUrlCommand*)command; +- (void)getToken:(CDVInvokedUrlCommand*)command; +- (void)getAPNSToken:(CDVInvokedUrlCommand*)command; +- (NSString *)hexadecimalStringFromData:(NSData *)data; +- (void)grantPermission:(CDVInvokedUrlCommand*)command; +- (void)hasPermission:(CDVInvokedUrlCommand*)command; +- (void)setBadgeNumber:(CDVInvokedUrlCommand*)command; +- (void)getBadgeNumber:(CDVInvokedUrlCommand*)command; +- (void)subscribe:(CDVInvokedUrlCommand*)command; +- (void)unsubscribe:(CDVInvokedUrlCommand*)command; +- (void)unregister:(CDVInvokedUrlCommand*)command; +- (void)onMessageReceived:(CDVInvokedUrlCommand*)command; +- (void)onTokenRefresh:(CDVInvokedUrlCommand*)command; +- (void)onApnsTokenReceived:(CDVInvokedUrlCommand *)command; +- (void)sendNotification:(NSDictionary*)userInfo; +- (void)sendToken:(NSString*)token; +- (void)sendApnsToken:(NSString*)token; +- (void)clearAllNotifications:(CDVInvokedUrlCommand *)command; + +// Analytics +- (void)setAnalyticsCollectionEnabled:(CDVInvokedUrlCommand*)command; +- (void)isAnalyticsCollectionEnabled:(CDVInvokedUrlCommand*)command; +- (void)logEvent:(CDVInvokedUrlCommand*)command; +- (void)setScreenName:(CDVInvokedUrlCommand*)command; +- (void)setUserId:(CDVInvokedUrlCommand*)command; +- (void)setUserProperty:(CDVInvokedUrlCommand*)command; + +// Crashlytics +- (void)setCrashlyticsCollectionEnabled:(CDVInvokedUrlCommand*)command; +- (void)isCrashlyticsCollectionEnabled:(CDVInvokedUrlCommand*)command; +- (void)logError:(CDVInvokedUrlCommand*)command; +- (void)logMessage:(CDVInvokedUrlCommand*)command; +- (void)sendCrash:(CDVInvokedUrlCommand*)command; +- (void)setCrashlyticsUserId:(CDVInvokedUrlCommand*)command; + +// Remote config +- (void)fetch:(CDVInvokedUrlCommand*)command; +- (void)activateFetched:(CDVInvokedUrlCommand*)command; +- (void)getValue:(CDVInvokedUrlCommand*)command; +- (void)getInfo:(CDVInvokedUrlCommand*)command; + +// Performance +- (void)setPerformanceCollectionEnabled:(CDVInvokedUrlCommand*)command; +- (void)isPerformanceCollectionEnabled:(CDVInvokedUrlCommand*)command; +- (void)startTrace:(CDVInvokedUrlCommand*)command; +- (void)incrementCounter:(CDVInvokedUrlCommand*)command; +- (void)stopTrace:(CDVInvokedUrlCommand*)command; + +// Firestore +- (void)addDocumentToFirestoreCollection:(CDVInvokedUrlCommand*)command; +- (void)setDocumentInFirestoreCollection:(CDVInvokedUrlCommand*)command; +- (void)updateDocumentInFirestoreCollection:(CDVInvokedUrlCommand*)command; +- (void)deleteDocumentFromFirestoreCollection:(CDVInvokedUrlCommand*)command; +- (void)documentExistsInFirestoreCollection:(CDVInvokedUrlCommand*)command; +- (void)fetchDocumentInFirestoreCollection:(CDVInvokedUrlCommand*)command; +- (void)fetchFirestoreCollection:(CDVInvokedUrlCommand*)command; + + +// Internals ++ (FirebasePlugin *) firebasePlugin; ++ (NSString*) appleSignInNonce; ++ (void) setFirestore:(FIRFirestore*) firestoreInstance; +- (void) handlePluginExceptionWithContext: (NSException*) exception :(CDVInvokedUrlCommand*)command; +- (void) handlePluginExceptionWithoutContext: (NSException*) exception; +- (void) _logError: (NSString*)msg; +- (void) _logInfo: (NSString*)msg; +- (void) _logMessage: (NSString*)msg; +- (BOOL) _shouldEnableCrashlytics; +- (int) saveAuthCredential: (FIRAuthCredential *) authCredential; +- (void)executeGlobalJavascript: (NSString*)jsString; + +- (void)createChannel:(CDVInvokedUrlCommand *)command; +- (void)setDefaultChannel:(CDVInvokedUrlCommand *)command; +- (void)deleteChannel:(CDVInvokedUrlCommand *)command; +- (void)listChannels:(CDVInvokedUrlCommand *)command; + +@property (nonatomic, copy) NSString *notificationCallbackId; +@property (nonatomic, copy) NSString *tokenRefreshCallbackId; +@property (nonatomic, copy) NSString *apnsTokenRefreshCallbackId; +@property (nonatomic, copy) NSString *googleSignInCallbackId; +@property (nonatomic, copy) NSString *appleSignInCallbackId; + +@property (nonatomic, retain) NSMutableArray *notificationStack; +@property (nonatomic, readwrite) NSMutableDictionary* traces; + +@end diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePlugin.m b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePlugin.m new file mode 100644 index 00000000..e72514c0 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePlugin.m @@ -0,0 +1,1757 @@ +#import "FirebasePlugin.h" +#import "FirebasePluginMessageReceiverManager.h" +#import "AppDelegate+FirebasePlugin.h" +#import +#import "AppDelegate.h" +#import +@import FirebaseInstanceID; +@import FirebaseMessaging; +@import FirebaseAnalytics; +@import FirebaseRemoteConfig; +@import FirebasePerformance; +@import FirebaseAuth; +@import UserNotifications; +@import CommonCrypto; +@import AuthenticationServices; + +@implementation FirebasePlugin + +@synthesize notificationCallbackId; +@synthesize tokenRefreshCallbackId; +@synthesize apnsTokenRefreshCallbackId; +@synthesize googleSignInCallbackId; +@synthesize appleSignInCallbackId; +@synthesize notificationStack; +@synthesize traces; + +static NSString*const LOG_TAG = @"FirebasePlugin[native]"; +static NSInteger const kNotificationStackSize = 10; +static NSString*const FIREBASE_CRASHLYTICS_COLLECTION_ENABLED = @"FIREBASE_CRASHLYTICS_COLLECTION_ENABLED"; //preference +static NSString*const FirebaseCrashlyticsCollectionEnabled = @"FirebaseCrashlyticsCollectionEnabled"; //plist +static NSString*const FIREBASE_ANALYTICS_COLLECTION_ENABLED = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED"; +static NSString*const FIREBASE_PERFORMANCE_COLLECTION_ENABLED = @"FIREBASE_PERFORMANCE_COLLECTION_ENABLED"; + +static FirebasePlugin* firebasePlugin; +static BOOL registeredForRemoteNotifications = NO; +static NSMutableDictionary* authCredentials; +static NSString* currentNonce; // used for Apple Sign In +static FIRFirestore* firestore; +static NSUserDefaults* preferences; +static NSDictionary* googlePlist; + + ++ (FirebasePlugin*) firebasePlugin { + return firebasePlugin; +} + ++ (NSString*) appleSignInNonce { + return currentNonce; +} + ++ (void) setFirestore:(FIRFirestore*) firestoreInstance{ + firestore = firestoreInstance; +} + +// @override abstract +- (void)pluginInitialize { + NSLog(@"Starting Firebase plugin"); + firebasePlugin = self; + + @try { + preferences = [NSUserDefaults standardUserDefaults]; + googlePlist = [NSMutableDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]]; + + if([self getGooglePlistFlagWithDefaultValue:FirebaseCrashlyticsCollectionEnabled defaultValue:YES]){ + [self setPreferenceFlag:FIREBASE_CRASHLYTICS_COLLECTION_ENABLED flag:YES]; + } + + if([self getGooglePlistFlagWithDefaultValue:FIREBASE_ANALYTICS_COLLECTION_ENABLED defaultValue:YES]){ + [self setPreferenceFlag:FIREBASE_ANALYTICS_COLLECTION_ENABLED flag:YES]; + } + + if([self getGooglePlistFlagWithDefaultValue:FIREBASE_PERFORMANCE_COLLECTION_ENABLED defaultValue:YES]){ + [self setPreferenceFlag:FIREBASE_PERFORMANCE_COLLECTION_ENABLED flag:YES]; + } + + // Set actionable categories if pn-actions.json exist in bundle + [self setActionableNotifications]; + + // Check for permission and register for remote notifications if granted + [self _hasPermission:^(BOOL result) {}]; + + [GIDSignIn sharedInstance].presentingViewController = self.viewController; + + authCredentials = [[NSMutableDictionary alloc] init]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithoutContext:exception]; + } +} + + +// Dynamic actions from pn-actions.json +- (void)setActionableNotifications { + + // Parse JSON + NSString *path = [[NSBundle mainBundle] pathForResource:@"pn-actions" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + + // Assign actions for categories + NSMutableSet *categories = [[NSMutableSet alloc] init]; + NSArray *actionsArray = [dict objectForKey:@"PushNotificationActions"]; + for (NSDictionary *item in actionsArray) { + NSMutableArray *buttons = [NSMutableArray new]; + NSString *category = [item objectForKey:@"category"]; + + NSArray *actions = [item objectForKey:@"actions"]; + for (NSDictionary *action in actions) { + NSString *actionId = [action objectForKey:@"id"]; + NSString *actionTitle = [action objectForKey:@"title"]; + + [buttons addObject:[UNNotificationAction actionWithIdentifier:actionId + title:NSLocalizedString(actionTitle, nil) options:UNNotificationActionOptionNone]]; + } + + [categories addObject:[UNNotificationCategory categoryWithIdentifier:category + actions:buttons intentIdentifiers:@[] options:UNNotificationCategoryOptionNone]]; + } + + // Initialize categories + [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categories]; +} + +// @override abstract +- (void)handleOpenURL:(NSNotification*)notification{ + NSURL* url = [notification object]; + [[GIDSignIn sharedInstance] handleURL:url]; +} + +- (void)setAutoInitEnabled:(CDVInvokedUrlCommand *)command { + @try { + bool enabled = [[command.arguments objectAtIndex:0] boolValue]; + [self runOnMainThread:^{ + @try { + [FIRMessaging messaging].autoInitEnabled = enabled; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)isAutoInitEnabled:(CDVInvokedUrlCommand *)command { + @try { + + [self runOnMainThread:^{ + @try { + bool enabled =[FIRMessaging messaging].isAutoInitEnabled; + + CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:enabled]; + [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +/* + * Remote notifications + */ + +- (void)getId:(CDVInvokedUrlCommand *)command { + __block CDVPluginResult *pluginResult; + + FIRInstanceIDHandler handler = ^(NSString *_Nullable instID, NSError *_Nullable error) { + @try { + [self handleStringResultWithPotentialError:error command:command result:instID]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }; + + @try { + [[FIRInstanceID instanceID] getIDWithHandler:handler]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)getToken:(CDVInvokedUrlCommand *)command { + @try { + [[FIRInstanceID instanceID] instanceIDWithHandler:^(FIRInstanceIDResult * _Nullable result, + NSError * _Nullable error) { + NSString* token = nil; + if (error == nil && result != nil && result.token != nil) { + token = result.token; + } + [self handleStringResultWithPotentialError:error command:command result:token]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)getAPNSToken:(CDVInvokedUrlCommand *)command { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[self getAPNSToken]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (NSString *)getAPNSToken { + NSString* hexToken = nil; + NSData* apnsToken = [FIRMessaging messaging].APNSToken; + if (apnsToken) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + // [deviceToken description] Starting with iOS 13 device token is like "{length = 32, bytes = 0xd3d997af 967d1f43 b405374a 13394d2f ... 28f10282 14af515f }" + hexToken = [self hexadecimalStringFromData:apnsToken]; +#else + hexToken = [[apnsToken.description componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet]invertedSet]]componentsJoinedByString:@""]; +#endif + } + return hexToken; +} + +- (NSString *)hexadecimalStringFromData:(NSData *)data +{ + NSUInteger dataLength = data.length; + if (dataLength == 0) { + return nil; + } + + const unsigned char *dataBuffer = data.bytes; + NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; + for (int i = 0; i < dataLength; ++i) { + [hexString appendFormat:@"%02x", dataBuffer[i]]; + } + return [hexString copy]; +} + +- (void)hasPermission:(CDVInvokedUrlCommand *)command { + @try { + [self _hasPermission:^(BOOL enabled) { + CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:enabled]; + [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +-(void)_hasPermission:(void (^)(BOOL result))completeBlock { + @try { + [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { + @try { + BOOL enabled = NO; + if (settings.alertSetting == UNNotificationSettingEnabled) { + enabled = YES; + [self registerForRemoteNotifications]; + } + NSLog(@"_hasPermission: %@", enabled ? @"YES" : @"NO"); + completeBlock(enabled); + }@catch (NSException *exception) { + [self handlePluginExceptionWithoutContext:exception]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithoutContext:exception]; + } +} + +- (void)grantPermission:(CDVInvokedUrlCommand *)command { + NSLog(@"grantPermission"); + @try { + [self _hasPermission:^(BOOL enabled) { + @try { + if(enabled){ + NSString* message = @"Permission is already granted - call hasPermission() to check before calling grantPermission()"; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }else{ + [UNUserNotificationCenter currentNotificationCenter].delegate = (id _Nullable) self; + UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert|UNAuthorizationOptionSound|UNAuthorizationOptionBadge; + [[UNUserNotificationCenter currentNotificationCenter] + requestAuthorizationWithOptions:authOptions + completionHandler:^(BOOL granted, NSError * _Nullable error) { + @try { + NSLog(@"requestAuthorizationWithOptions: granted=%@", granted ? @"YES" : @"NO"); + if (error == nil && granted) { + [self registerForRemoteNotifications]; + } + [self handleBoolResultWithPotentialError:error command:command result:granted]; + + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + } + ]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)registerForRemoteNotifications { + NSLog(@"registerForRemoteNotifications"); + if(registeredForRemoteNotifications) return; + + [self runOnMainThread:^{ + @try { + [[UIApplication sharedApplication] registerForRemoteNotifications]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithoutContext:exception]; + } + registeredForRemoteNotifications = YES; + }]; +} + +- (void)setBadgeNumber:(CDVInvokedUrlCommand *)command { + @try { + int number = [[command.arguments objectAtIndex:0] intValue]; + [self runOnMainThread:^{ + @try { + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:number]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)getBadgeNumber:(CDVInvokedUrlCommand *)command { + [self runOnMainThread:^{ + @try { + long badge = [[UIApplication sharedApplication] applicationIconBadgeNumber]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:badge]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)subscribe:(CDVInvokedUrlCommand *)command { + @try { + NSString* topic = [NSString stringWithFormat:@"%@", [command.arguments objectAtIndex:0]]; + + [[FIRMessaging messaging] subscribeToTopic: topic completion:^(NSError * _Nullable error) { + [self handleEmptyResultWithPotentialError:error command:command]; + }]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)unsubscribe:(CDVInvokedUrlCommand *)command { + @try { + NSString* topic = [NSString stringWithFormat:@"%@", [command.arguments objectAtIndex:0]]; + + [[FIRMessaging messaging] unsubscribeFromTopic: topic completion:^(NSError * _Nullable error) { + [self handleEmptyResultWithPotentialError:error command:command]; + }]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)unregister:(CDVInvokedUrlCommand *)command { + @try { + [[FIRInstanceID instanceID] deleteIDWithHandler:^void(NSError *_Nullable error) { + [self handleEmptyResultWithPotentialError:error command:command]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + + + +- (void)onMessageReceived:(CDVInvokedUrlCommand *)command { + @try { + self.notificationCallbackId = command.callbackId; + + if (self.notificationStack != nil && [self.notificationStack count]) { + for (NSDictionary *userInfo in self.notificationStack) { + [self sendNotification:userInfo]; + } + [self.notificationStack removeAllObjects]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)onTokenRefresh:(CDVInvokedUrlCommand *)command { + self.tokenRefreshCallbackId = command.callbackId; + @try { + [[FIRInstanceID instanceID] instanceIDWithHandler:^(FIRInstanceIDResult * _Nullable result, + NSError * _Nullable error) { + @try { + if (result.token != nil && error == nil) { + [self sendToken:result.token]; + }else{ + [self handleStringResultWithPotentialError:error command:command result:result.token]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)onApnsTokenReceived:(CDVInvokedUrlCommand *)command { + self.apnsTokenRefreshCallbackId = command.callbackId; + @try { + NSString* apnsToken = [self getAPNSToken]; + if(apnsToken != nil){ + [self sendApnsToken:apnsToken]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)sendNotification:(NSDictionary *)userInfo { + @try { + if([FirebasePluginMessageReceiverManager sendNotification:userInfo]){ + [self _logMessage:@"Message handled by custom receiver"]; + return; + } + if (self.notificationCallbackId != nil) { + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:userInfo]; + [pluginResult setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.notificationCallbackId]; + } else { + if (!self.notificationStack) { + self.notificationStack = [[NSMutableArray alloc] init]; + } + + // stack notifications until a callback has been registered + [self.notificationStack addObject:userInfo]; + + if ([self.notificationStack count] >= kNotificationStackSize) { + [self.notificationStack removeLastObject]; + } + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :self.commandDelegate]; + } +} + +- (void)sendToken:(NSString *)token { + @try { + if (self.tokenRefreshCallbackId != nil) { + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:token]; + [pluginResult setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.tokenRefreshCallbackId]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :self.commandDelegate]; + } +} + +- (void)sendApnsToken:(NSString *)token { + @try { + if (self.apnsTokenRefreshCallbackId != nil) { + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:token]; + [pluginResult setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.apnsTokenRefreshCallbackId]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :self.commandDelegate]; + } +} + +- (void)clearAllNotifications:(CDVInvokedUrlCommand *)command { + [self runOnMainThread:^{ + @try { + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:1]; + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +/* + * Authentication + */ +- (void)verifyPhoneNumber:(CDVInvokedUrlCommand *)command { + NSString* number = [command.arguments objectAtIndex:0]; + + @try { + [[FIRPhoneAuthProvider provider] + verifyPhoneNumber:number + UIDelegate:nil + completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) { + + @try { + CDVPluginResult* pluginResult; + if (error) { + // Verification code not sent. + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description]; + } else { + // Successful. + NSMutableDictionary* result = [[NSMutableDictionary alloc] init]; + [result setValue:@"false" forKey:@"instantVerification"]; + [result setValue:verificationID forKey:@"verificationId"]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)createUserWithEmailAndPassword:(CDVInvokedUrlCommand*)command { + @try { + NSString* email = [command.arguments objectAtIndex:0]; + NSString* password = [command.arguments objectAtIndex:1]; + [[FIRAuth auth] createUserWithEmail:email + password:password + completion:^(FIRAuthDataResult * _Nullable authResult, + NSError * _Nullable error) { + @try { + [self handleAuthResult:authResult error:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)signInUserWithEmailAndPassword:(CDVInvokedUrlCommand*)command { + @try { + NSString* email = [command.arguments objectAtIndex:0]; + NSString* password = [command.arguments objectAtIndex:1]; + [[FIRAuth auth] signInWithEmail:email + password:password + completion:^(FIRAuthDataResult * _Nullable authResult, + NSError * _Nullable error) { + @try { + [self handleAuthResult:authResult error:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)signInUserWithCustomToken:(CDVInvokedUrlCommand*)command { + @try { + NSString* customToken = [command.arguments objectAtIndex:0]; + [[FIRAuth auth] signInWithCustomToken:customToken + completion:^(FIRAuthDataResult * _Nullable authResult, + NSError * _Nullable error) { + @try { + [self handleAuthResult:authResult error:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)signInUserAnonymously:(CDVInvokedUrlCommand*)command { + @try { + [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRAuthDataResult * _Nullable authResult, + NSError * _Nullable error) { + @try { + [self handleAuthResult:authResult error:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)authenticateUserWithGoogle:(CDVInvokedUrlCommand*)command{ + @try { + self.googleSignInCallbackId = command.callbackId; + [[GIDSignIn sharedInstance] signIn]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; + [pluginResult setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)authenticateUserWithApple:(CDVInvokedUrlCommand*)command{ + @try { + CDVPluginResult *pluginResult; + if (@available(iOS 13.0, *)) { + self.appleSignInCallbackId = command.callbackId; + [self startSignInWithAppleFlow]; + + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; + [pluginResult setKeepCallbackAsBool:YES]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"OS version is too low - Apple Sign In requires iOS 13.0+"]; + } + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)signInWithCredential:(CDVInvokedUrlCommand*)command { + @try { + FIRAuthCredential* credential = [self obtainAuthCredential:[command.arguments objectAtIndex:0] command:command]; + if(credential == nil) return; + + [[FIRAuth auth] signInWithCredential:credential + completion:^(FIRAuthDataResult * _Nullable authResult, + NSError * _Nullable error) { + [self handleAuthResult:authResult error:error command:command]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)reauthenticateWithCredential:(CDVInvokedUrlCommand*)command{ + @try { + FIRUser* user = [FIRAuth auth].currentUser; + if(!user){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + + FIRAuthCredential* credential = [self obtainAuthCredential:[command.arguments objectAtIndex:0] command:command]; + if(credential == nil) return; + + [user reauthenticateWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + [self handleAuthResult:authResult error:error command:command]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)linkUserWithCredential:(CDVInvokedUrlCommand*)command { + @try { + FIRAuthCredential* credential = [self obtainAuthCredential:[command.arguments objectAtIndex:0] command:command]; + if(credential == nil) return; + + [[FIRAuth auth].currentUser linkWithCredential:credential + completion:^(FIRAuthDataResult * _Nullable authResult, + NSError * _Nullable error) { + [self handleAuthResult:authResult error:error command:command]; + }]; + + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)isUserSignedIn:(CDVInvokedUrlCommand*)command { + @try { + bool isSignedIn = [FIRAuth auth].currentUser ? true : false; + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:isSignedIn] callbackId:command.callbackId]; + + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)signOutUser:(CDVInvokedUrlCommand*)command { + @try { + bool isSignedIn = [FIRAuth auth].currentUser ? true : false; + if(!isSignedIn){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + + // Sign out of Google + if([[GIDSignIn sharedInstance] currentUser] != nil){ + [[GIDSignIn sharedInstance] signOut]; + } + + // Sign out of Firebase + NSError *signOutError; + BOOL status = [[FIRAuth auth] signOut:&signOutError]; + if (!status) { + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[NSString stringWithFormat:@"Error signing out: %@", signOutError]] callbackId:command.callbackId]; + }else{ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)getCurrentUser:(CDVInvokedUrlCommand *)command { + + @try { + FIRUser* user = [FIRAuth auth].currentUser; + if(!user){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + [self extractAndReturnUserInfo:command]; + + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)reloadCurrentUser:(CDVInvokedUrlCommand *)command { + + @try { + FIRUser* user = [FIRAuth auth].currentUser; + if(!user){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + [user reloadWithCompletion:^(NSError * _Nullable error) { + if (error != nil) { + [self handleEmptyResultWithPotentialError:error command:command]; + }else { + [self extractAndReturnUserInfo:command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void) extractAndReturnUserInfo:(CDVInvokedUrlCommand *)command { + FIRUser* user = [FIRAuth auth].currentUser; + NSMutableDictionary* userInfo = [NSMutableDictionary new]; + [userInfo setValue:user.displayName forKey:@"name"]; + [userInfo setValue:user.email forKey:@"email"]; + [userInfo setValue:@(user.isEmailVerified ? true : false) forKey:@"emailIsVerified"]; + [userInfo setValue:user.phoneNumber forKey:@"phoneNumber"]; + [userInfo setValue:user.photoURL ? user.photoURL.absoluteString : nil forKey:@"photoUrl"]; + [userInfo setValue:user.uid forKey:@"uid"]; + [userInfo setValue:@(user.isAnonymous ? true : false) forKey:@"isAnonymous"]; + [user getIDTokenWithCompletion:^(NSString * _Nullable token, NSError * _Nullable error) { + [userInfo setValue:token forKey:@"idToken"]; + [user getIDTokenResultWithCompletion:^(FIRAuthTokenResult * _Nullable tokenResult, NSError * _Nullable error) { + [userInfo setValue:tokenResult.signInProvider forKey:@"providerId"]; + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:userInfo] callbackId:command.callbackId]; + }]; + }]; +} + +- (void)updateUserProfile:(CDVInvokedUrlCommand*)command { + @try { + FIRUser* user = [FIRAuth auth].currentUser; + if(!user){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + + NSDictionary* profile = [command.arguments objectAtIndex:0]; + + FIRUserProfileChangeRequest* changeRequest = [user profileChangeRequest]; + if([profile objectForKey:@"name"] != nil){ + changeRequest.displayName = [profile objectForKey:@"name"]; + } + if([profile objectForKey:@"photoUri"] != nil){ + changeRequest.photoURL = [NSURL URLWithString:[profile objectForKey:@"photoUri"]]; + } + + [changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) { + @try { + [self handleEmptyResultWithPotentialError:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)updateUserEmail:(CDVInvokedUrlCommand*)command { + @try { + FIRUser* user = [FIRAuth auth].currentUser; + if(!user){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + + NSString* email = [command.arguments objectAtIndex:0]; + [user updateEmail:email completion:^(NSError *_Nullable error) { + @try { + [self handleEmptyResultWithPotentialError:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)sendUserEmailVerification:(CDVInvokedUrlCommand*)command{ + @try { + FIRUser* user = [FIRAuth auth].currentUser; + if(!user){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + + [user sendEmailVerificationWithCompletion:^(NSError *_Nullable error) { + @try { + [self handleEmptyResultWithPotentialError:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)updateUserPassword:(CDVInvokedUrlCommand*)command{ + @try { + FIRUser* user = [FIRAuth auth].currentUser; + if(!user){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + + NSString* password = [command.arguments objectAtIndex:0]; + [user updatePassword:password completion:^(NSError *_Nullable error) { + @try { + [self handleEmptyResultWithPotentialError:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)sendUserPasswordResetEmail:(CDVInvokedUrlCommand*)command{ + @try { + NSString* email = [command.arguments objectAtIndex:0]; + [[FIRAuth auth] sendPasswordResetWithEmail:email completion:^(NSError *_Nullable error) { + @try { + [self handleEmptyResultWithPotentialError:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)deleteUser:(CDVInvokedUrlCommand*)command{ + @try { + FIRUser* user = [FIRAuth auth].currentUser; + if(!user){ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No user is currently signed"] callbackId:command.callbackId]; + return; + } + + [user deleteWithCompletion:^(NSError *_Nullable error) { + @try { + [self handleEmptyResultWithPotentialError:error command:command]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)startSignInWithAppleFlow API_AVAILABLE(ios(13.0)){ + NSString *nonce = [self randomNonce:32]; + currentNonce = nonce; + ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init]; + ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest]; + request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail]; + request.nonce = [self stringBySha256HashingString:nonce]; + + ASAuthorizationController *authorizationController = + [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]]; + authorizationController.delegate = [AppDelegate instance]; + authorizationController.presentationContextProvider = [AppDelegate instance]; + [authorizationController performRequests]; +} + +- (NSString *)stringBySha256HashingString:(NSString *)input { + const char *string = [input UTF8String]; + unsigned char result[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(string, (CC_LONG)strlen(string), result); + + NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { + [hashed appendFormat:@"%02x", result[i]]; + } + return hashed; +} + +// Generates random nonce for Apple Sign In +- (NSString *)randomNonce:(NSInteger)length { + NSAssert(length > 0, @"Expected nonce to have positive length"); + NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._"; + NSMutableString *result = [NSMutableString string]; + NSInteger remainingLength = length; + + while (remainingLength > 0) { + NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16]; + for (NSInteger i = 0; i < 16; i++) { + uint8_t random = 0; + int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random); + NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode); + + [randoms addObject:@(random)]; + } + + for (NSNumber *random in randoms) { + if (remainingLength == 0) { + break; + } + + if (random.unsignedIntValue < characterSet.length) { + unichar character = [characterSet characterAtIndex:random.unsignedIntValue]; + [result appendFormat:@"%C", character]; + remainingLength--; + } + } + } + + return result; +} + +/* + * Analytics + */ +- (void)setAnalyticsCollectionEnabled:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + BOOL enabled = [[command argumentAtIndex:0] boolValue]; + CDVPluginResult* pluginResult; + [FIRAnalytics setAnalyticsCollectionEnabled:enabled]; + [self setPreferenceFlag:FIREBASE_ANALYTICS_COLLECTION_ENABLED flag:enabled]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)isAnalyticsCollectionEnabled:(CDVInvokedUrlCommand*)command{ + [self.commandDelegate runInBackground:^{ + @try { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:[self getPreferenceFlag:FIREBASE_ANALYTICS_COLLECTION_ENABLED]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)logEvent:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* name = [command.arguments objectAtIndex:0]; + NSDictionary *parameters = [command argumentAtIndex:1]; + + [FIRAnalytics logEventWithName:name parameters:parameters]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)setScreenName:(CDVInvokedUrlCommand *)command { + @try { + NSString* name = [command.arguments objectAtIndex:0]; + + [FIRAnalytics setScreenName:name screenClass:NULL]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (void)setUserId:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* id = [command.arguments objectAtIndex:0]; + + [FIRAnalytics setUserID:id]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)setUserProperty:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* name = [command.arguments objectAtIndex:0]; + NSString* value = [command.arguments objectAtIndex:1]; + + [FIRAnalytics setUserPropertyString:value forName:name]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +/* + * Crashlytics + */ + +- (void)setCrashlyticsCollectionEnabled:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + BOOL enabled = [[command argumentAtIndex:0] boolValue]; + CDVPluginResult* pluginResult; + [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:enabled]; + [self setPreferenceFlag:FIREBASE_CRASHLYTICS_COLLECTION_ENABLED flag:enabled]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)isCrashlyticsCollectionEnabled:(CDVInvokedUrlCommand*)command{ + [self.commandDelegate runInBackground:^{ + @try { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:[self isCrashlyticsEnabled]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +-(BOOL)isCrashlyticsEnabled{ + return [self getPreferenceFlag:FIREBASE_CRASHLYTICS_COLLECTION_ENABLED]; +} + +- (void)logError:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + NSString* errorMessage = [command.arguments objectAtIndex:0]; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + @try { + if(![self isCrashlyticsEnabled]){ + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Cannot log error - Crashlytics collection is disabled"]; + } + // We can optionally be passed a stack trace from stackTrace.js which we'll put in userInfo. + else if ([command.arguments count] > 1) { + NSArray* stackFrames = [command.arguments objectAtIndex:1]; + + NSString* message = errorMessage; + NSString* name = @"Uncaught Javascript exception"; + NSMutableArray *customFrames = [[NSMutableArray alloc] init]; + FIRExceptionModel *exceptionModel = [FIRExceptionModel exceptionModelWithName:name reason:message]; + + for (NSDictionary* stackFrame in stackFrames) { + FIRStackFrame *customFrame = [FIRStackFrame stackFrameWithSymbol:stackFrame[@"functionName"] file:stackFrame[@"fileName"] line:(uint32_t) [stackFrame[@"lineNumber"] intValue]]; + [customFrames addObject:customFrame]; + } + exceptionModel.stackTrace = customFrames; + [[FIRCrashlytics crashlytics] recordExceptionModel:exceptionModel]; + }else{ + //TODO detect and handle non-stack userInfo and pass to recordError + NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init]; + NSError *error = [NSError errorWithDomain:errorMessage code:0 userInfo:userInfo]; + [[FIRCrashlytics crashlytics] recordError:error]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } @catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + + }]; +} + +- (void)logMessage:(CDVInvokedUrlCommand*)command{ + [self.commandDelegate runInBackground:^{ + @try { + NSString* message = [command argumentAtIndex:0 withDefault:@""]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + if(![self isCrashlyticsEnabled]){ + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Cannot log message - Crashlytics collection is disabled"]; + }else if(message){ + [[FIRCrashlytics crashlytics] logWithFormat:@"%@", message]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)sendCrash:(CDVInvokedUrlCommand*)command{ + assert(NO); +} + +- (void)setCrashlyticsUserId:(CDVInvokedUrlCommand *)command { + @try { + NSString* userId = [command.arguments objectAtIndex:0]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + if(![self isCrashlyticsEnabled]){ + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Cannot set user ID - Crashlytics collection is disabled"]; + }else{ + [[FIRCrashlytics crashlytics] setUserID:userId]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +/* + * Remote config + */ +- (void)fetch:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + FIRRemoteConfig* remoteConfig = [FIRRemoteConfig remoteConfig]; + + if ([command.arguments count] > 0) { + int expirationDuration = [[command.arguments objectAtIndex:0] intValue]; + + [remoteConfig fetchWithExpirationDuration:expirationDuration completionHandler:^(FIRRemoteConfigFetchStatus status, NSError * _Nullable error) { + if (status == FIRRemoteConfigFetchStatusSuccess && error == nil){ + [self sendPluginSuccess:command]; + }else if (error != nil) { + [self handleEmptyResultWithPotentialError:error command:command]; + } else { + [self sendPluginError:command]; + } + }]; + } else { + [remoteConfig fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError * _Nullable error) { + if (status == FIRRemoteConfigFetchStatusSuccess && error == nil){ + [self sendPluginSuccess:command]; + }else if (error != nil) { + [self handleEmptyResultWithPotentialError:error command:command]; + } else { + [self sendPluginError:command]; + } + }]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)activateFetched:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + FIRRemoteConfig* remoteConfig = [FIRRemoteConfig remoteConfig]; + [remoteConfig activateWithCompletion:^(BOOL changed, NSError* _Nullable error) { + [self handleBoolResultWithPotentialError:error command:command result:true]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)getValue:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* key = [command.arguments objectAtIndex:0]; + FIRRemoteConfig* remoteConfig = [FIRRemoteConfig remoteConfig]; + NSString* value = remoteConfig[key].stringValue; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:value]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)getInfo:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + FIRRemoteConfig* remoteConfig = [FIRRemoteConfig remoteConfig]; + NSInteger minimumFetchInterval = remoteConfig.configSettings.minimumFetchInterval; + NSInteger fetchTimeout = remoteConfig.configSettings.fetchTimeout; + NSDate* lastFetchTime = remoteConfig.lastFetchTime; + FIRRemoteConfigFetchStatus lastFetchStatus = remoteConfig.lastFetchStatus; + // isDeveloperModeEnabled is deprecated new recommnded way to check is minimumFetchInterval == 0 + BOOL isDeveloperModeEnabled = minimumFetchInterval == 0 ? true : false; + + NSDictionary* configSettings = @{ + @"developerModeEnabled": [NSNumber numberWithBool:isDeveloperModeEnabled], + @"minimumFetchInterval": [NSNumber numberWithInteger:minimumFetchInterval], + @"fetchTimeout": [NSNumber numberWithInteger:fetchTimeout], + }; + + NSDictionary* infoObject = @{ + @"configSettings": configSettings, + @"fetchTimeMillis": (lastFetchTime ? [NSNumber numberWithInteger:(lastFetchTime.timeIntervalSince1970 * 1000)] : [NSNull null]), + @"lastFetchStatus": [NSNumber numberWithInteger:(lastFetchStatus)], + }; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:infoObject]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +/* + * Performance + */ +- (void)setPerformanceCollectionEnabled:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + BOOL enabled = [[command argumentAtIndex:0] boolValue]; + CDVPluginResult* pluginResult; + [[FIRPerformance sharedInstance] setDataCollectionEnabled:enabled]; + [self setPreferenceFlag:FIREBASE_PERFORMANCE_COLLECTION_ENABLED flag:enabled]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)isPerformanceCollectionEnabled:(CDVInvokedUrlCommand*)command{ + [self.commandDelegate runInBackground:^{ + @try { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:[self getPreferenceFlag:FIREBASE_PERFORMANCE_COLLECTION_ENABLED]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)startTrace:(CDVInvokedUrlCommand *)command { + + [self.commandDelegate runInBackground:^{ + @try { + NSString* traceName = [command.arguments objectAtIndex:0]; + FIRTrace *trace = [self.traces objectForKey:traceName]; + + if ( self.traces == nil) { + self.traces = [NSMutableDictionary new]; + } + + if (trace == nil) { + trace = [FIRPerformance startTraceWithName:traceName]; + [self.traces setObject:trace forKey:traceName ]; + + } + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)incrementCounter:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* traceName = [command.arguments objectAtIndex:0]; + NSString* counterNamed = [command.arguments objectAtIndex:1]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + FIRTrace *trace = (FIRTrace*)[self.traces objectForKey:traceName]; + + if (trace != nil) { + [trace incrementMetric:counterNamed byInt:1]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Trace not found"]; + } + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)stopTrace:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* traceName = [command.arguments objectAtIndex:0]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + FIRTrace *trace = [self.traces objectForKey:traceName]; + + if (trace != nil) { + [trace stop]; + [self.traces removeObjectForKey:traceName]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Trace not found"]; + } + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +/* +* Firestore +*/ + +- (void)addDocumentToFirestoreCollection:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSDictionary* document = [command.arguments objectAtIndex:0]; + NSString* collection = [command.arguments objectAtIndex:1]; + __block FIRDocumentReference *ref = + [[firestore collectionWithPath:collection] addDocumentWithData:document completion:^(NSError * _Nullable error) { + [self handleStringResultWithPotentialError:error command:command result:ref.documentID]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)setDocumentInFirestoreCollection:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* documentId = [command.arguments objectAtIndex:0]; + NSDictionary* document = [command.arguments objectAtIndex:1]; + NSString* collection = [command.arguments objectAtIndex:2]; + + [[[firestore collectionWithPath:collection] documentWithPath:documentId] setData:document completion:^(NSError * _Nullable error) { + [self handleEmptyResultWithPotentialError:error command:command]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)updateDocumentInFirestoreCollection:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* documentId = [command.arguments objectAtIndex:0]; + NSDictionary* document = [command.arguments objectAtIndex:1]; + NSString* collection = [command.arguments objectAtIndex:2]; + + FIRDocumentReference* docRef = [[firestore collectionWithPath:collection] documentWithPath:documentId]; + if(docRef != nil){ + [docRef updateData:document completion:^(NSError * _Nullable error) { + [self handleEmptyResultWithPotentialError:error command:command]; + }]; + }else{ + [self sendPluginErrorWithMessage:@"Document not found in collection":command]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)deleteDocumentFromFirestoreCollection:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* documentId = [command.arguments objectAtIndex:0]; + NSString* collection = [command.arguments objectAtIndex:1]; + + [[[firestore collectionWithPath:collection] documentWithPath:documentId] + deleteDocumentWithCompletion:^(NSError * _Nullable error) { + [self handleEmptyResultWithPotentialError:error command:command]; + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)documentExistsInFirestoreCollection:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* documentId = [command.arguments objectAtIndex:0]; + NSString* collection = [command.arguments objectAtIndex:1]; + + FIRDocumentReference* docRef = [[firestore collectionWithPath:collection] documentWithPath:documentId]; + if(docRef != nil){ + [docRef getDocumentWithCompletion:^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) { + BOOL docExists = snapshot.data != nil; + [self handleBoolResultWithPotentialError:error command:command result:docExists]; + }]; + }else{ + [self sendPluginErrorWithMessage:@"Collection not found":command]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)fetchDocumentInFirestoreCollection:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* documentId = [command.arguments objectAtIndex:0]; + NSString* collection = [command.arguments objectAtIndex:1]; + + FIRDocumentReference* docRef = [[firestore collectionWithPath:collection] documentWithPath:documentId]; + if(docRef != nil){ + [docRef getDocumentWithCompletion:^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) { + if (error != nil) { + [self sendPluginErrorWithMessage:error.localizedDescription:command]; + } else if(snapshot.data != nil) { + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:snapshot.data] callbackId:command.callbackId]; + }else{ + [self sendPluginErrorWithMessage:@"Document not found in collection":command]; + } + }]; + }else{ + [self sendPluginErrorWithMessage:@"Collection not found":command]; + } + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +- (void)fetchFirestoreCollection:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* collection = [command.arguments objectAtIndex:0]; + NSArray* filters = [command.arguments objectAtIndex:1]; + FIRQuery* query = [firestore collectionWithPath:collection]; + + for (int i = 0; i < [filters count]; i++) { + NSArray* filter = [filters objectAtIndex:i]; + if ([[filter objectAtIndex:0] isEqualToString:@"where"]) { + if ([[filter objectAtIndex:2] isEqualToString:@"=="]) { + query = [query queryWhereField:[filter objectAtIndex:1] isEqualTo:[filter objectAtIndex:3]]; + } + if ([[filter objectAtIndex:2] isEqualToString:@"<"]) { + query = [query queryWhereField:[filter objectAtIndex:1] isLessThan:[filter objectAtIndex:3]]; + } + if ([[filter objectAtIndex:2] isEqualToString:@">"]) { + query = [query queryWhereField:[filter objectAtIndex:1] isGreaterThan:[filter objectAtIndex:3]]; + } + if ([[filter objectAtIndex:2] isEqualToString:@"<="]) { + query = [query queryWhereField:[filter objectAtIndex:1] isLessThanOrEqualTo:[filter objectAtIndex:3]]; + } + if ([[filter objectAtIndex:2] isEqualToString:@">="]) { + query = [query queryWhereField:[filter objectAtIndex:1] isGreaterThanOrEqualTo:[filter objectAtIndex:3]]; + } + if ([[filter objectAtIndex:2] isEqualToString:@"array-contains"]) { + query = [query queryWhereField:[filter objectAtIndex:1] arrayContains:[filter objectAtIndex:3]]; + } + continue; + } + if ([[filter objectAtIndex:0] isEqualToString:@"orderBy"]) { + query = [query queryOrderedByField:[filter objectAtIndex:1] descending:([[filter objectAtIndex:2] isEqualToString:@"desc"])]; + continue; + } + if ([[filter objectAtIndex:0] isEqualToString:@"startAt"]) { + query = [query queryStartingAtValues:[filter objectAtIndex:1]]; + continue; + } + if ([[filter objectAtIndex:0] isEqualToString:@"endAt"]) { + query = [query queryEndingAtValues:[filter objectAtIndex:1]]; + continue; + } + if ([[filter objectAtIndex:0] isEqualToString:@"limit"]) { + query = [query queryLimitedTo:[(NSNumber *)[filter objectAtIndex:1] integerValue]]; + continue; + } + } + + [query getDocumentsWithCompletion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { + if (error != nil) { + [self sendPluginErrorWithMessage:error.localizedDescription:command]; + } else { + NSMutableDictionary* documents = [[NSMutableDictionary alloc] init];; + for (FIRDocumentSnapshot *document in snapshot.documents) { + [documents setObject:document.data forKey:document.documentID]; + } + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:documents] callbackId:command.callbackId]; + } + }]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } + }]; +} + +/********************************/ +#pragma mark - utility functions +/********************************/ +- (void) sendPluginSuccess:(CDVInvokedUrlCommand*)command{ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId]; +} + +- (void) sendPluginError:(CDVInvokedUrlCommand*)command{ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR] callbackId:command.callbackId]; +} + +- (void) sendPluginErrorWithMessage: (NSString*) errorMessage :(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; + [self _logError:errorMessage]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void) sendPluginErrorWithError:(NSError*)error command:(CDVInvokedUrlCommand*)command{ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description] callbackId:command.callbackId]; +} + +- (void) handleEmptyResultWithPotentialError:(NSError*) error command:(CDVInvokedUrlCommand*)command { + if (error) { + [self sendPluginErrorWithError:error command:command]; + }else{ + [self sendPluginSuccess:command]; + } +} + +- (void) handleStringResultWithPotentialError:(NSError*) error command:(CDVInvokedUrlCommand*)command result:(NSString*)result { + if (error) { + [self sendPluginErrorWithError:error command:command]; + }else{ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:result] callbackId:command.callbackId]; + } +} + +- (void) handleBoolResultWithPotentialError:(NSError*) error command:(CDVInvokedUrlCommand*)command result:(BOOL)result { + if (error) { + [self sendPluginErrorWithError:error command:command]; + }else{ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:result] callbackId:command.callbackId]; + } +} + +- (void) handlePluginExceptionWithContext: (NSException*) exception :(CDVInvokedUrlCommand*)command +{ + [self handlePluginExceptionWithoutContext:exception]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void) handlePluginExceptionWithoutContext: (NSException*) exception +{ + [self _logError:[NSString stringWithFormat:@"EXCEPTION: %@", exception.reason]]; +} + +- (void)executeGlobalJavascript: (NSString*)jsString +{ + [self.commandDelegate evalJs:jsString]; +} + +- (void)_logError: (NSString*)msg +{ + NSLog(@"%@ ERROR: %@", LOG_TAG, msg); + NSString* jsString = [NSString stringWithFormat:@"console.error(\"%@: %@\")", LOG_TAG, [self escapeJavascriptString:msg]]; + [self executeGlobalJavascript:jsString]; +} + +- (void)_logInfo: (NSString*)msg +{ + NSLog(@"%@ INFO: %@", LOG_TAG, msg); + NSString* jsString = [NSString stringWithFormat:@"console.info(\"%@: %@\")", LOG_TAG, [self escapeJavascriptString:msg]]; + [self executeGlobalJavascript:jsString]; +} + +- (void)_logMessage: (NSString*)msg +{ + NSLog(@"%@ LOG: %@", LOG_TAG, msg); + NSString* jsString = [NSString stringWithFormat:@"console.log(\"%@: %@\")", LOG_TAG, [self escapeJavascriptString:msg]]; + [self executeGlobalJavascript:jsString]; +} + +- (NSString*)escapeJavascriptString: (NSString*)str +{ + NSString* result = [str stringByReplacingOccurrencesOfString: @"\\\"" withString: @"\""]; + result = [result stringByReplacingOccurrencesOfString: @"\"" withString: @"\\\""]; + result = [result stringByReplacingOccurrencesOfString: @"\n" withString: @"\\\n"]; + return result; +} + +- (void)runOnMainThread:(void (^)(void))completeBlock { + if (![NSThread isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + @try { + completeBlock(); + }@catch (NSException *exception) { + [self handlePluginExceptionWithoutContext:exception]; + } + }); + } else { + @try { + completeBlock(); + }@catch (NSException *exception) { + [self handlePluginExceptionWithoutContext:exception]; + } + } +} + +- (FIRAuthCredential*)obtainAuthCredential:(NSDictionary*)credential command:(CDVInvokedUrlCommand *)command { + FIRAuthCredential* authCredential = nil; + + if(credential == nil){ + NSString* errMsg = @"credential object must be passed as first and only argument"; + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errMsg] callbackId:command.callbackId]; + return authCredential; + } + + NSString* key = [credential objectForKey:@"id"]; + NSString* verificationId = [credential objectForKey:@"verificationId"]; + NSString* code = [credential objectForKey:@"code"]; + + if(key != nil){ + authCredential = [authCredentials objectForKey:key]; + if(authCredential == nil){ + NSString* errMsg = [NSString stringWithFormat:@"no native auth credential exists for specified id '%@'", key]; + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errMsg] callbackId:command.callbackId]; + } + }else if(verificationId != nil && code != nil){ + authCredential = [[FIRPhoneAuthProvider provider] + credentialWithVerificationID:verificationId + verificationCode:code]; + }else{ + NSString* errMsg = @"credential object must either specify the id key of an existing native auth credential or the verificationId/code keys must be specified for a phone number authentication"; + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errMsg] callbackId:command.callbackId]; + } + + return authCredential; +} + +- (void) handleAuthResult:(FIRAuthDataResult*) authResult error:(NSError*) error command:(CDVInvokedUrlCommand*)command { + @try { + CDVPluginResult* pluginResult; + if (error) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description]; + }else if (authResult == nil) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"User not signed in"]; + }else{ + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }@catch (NSException *exception) { + [self handlePluginExceptionWithContext:exception :command]; + } +} + +- (int) saveAuthCredential: (FIRAuthCredential*) authCredential { + int key = -1; + while (key < 0 || [authCredentials objectForKey:[NSNumber numberWithInt:key]] != nil) { + key = arc4random_uniform(100000); + } + + [authCredentials setObject:authCredential forKey:[NSNumber numberWithInt:key]]; + + return key; +} + +- (void) setPreferenceFlag:(NSString*) name flag:(BOOL)flag { + [preferences setBool:flag forKey:name]; + [preferences synchronize]; +} + +- (BOOL) getPreferenceFlag:(NSString*) name { + if([preferences objectForKey:name] == nil){ + return false; + } + return [preferences boolForKey:name]; +} + +- (BOOL) getGooglePlistFlagWithDefaultValue:(NSString*) name defaultValue:(BOOL)defaultValue { + if([googlePlist objectForKey:name] == nil){ + return defaultValue; + } + return [[googlePlist objectForKey:name] isEqualToString:@"true"]; +} + + +# pragma mark - Stubs +- (void)createChannel:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; +} + +- (void)setDefaultChannel:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; +} + +- (void)deleteChannel:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; +} + +- (void)listChannels:(CDVInvokedUrlCommand *)command { + [self.commandDelegate runInBackground:^{ + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; +} +@end diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiver.h b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiver.h new file mode 100644 index 00000000..639de9ba --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiver.h @@ -0,0 +1,5 @@ +#import + +@interface FirebasePluginMessageReceiver : NSObject {} +- (bool) sendNotification:(NSDictionary *)userInfo; +@end diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiver.m b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiver.m new file mode 100644 index 00000000..0990d8c1 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiver.m @@ -0,0 +1,17 @@ +#import "FirebasePluginMessageReceiver.h" +#import "FirebasePluginMessageReceiverManager.h" + +@implementation FirebasePluginMessageReceiver + +- (id) init { + [FirebasePluginMessageReceiverManager register:self]; + return self; +} + +// Concrete subclasses should override this and return true if they handle the received message. +- (bool) sendNotification:(NSDictionary *)userInfo { + NSAssert(false, @"You cannot call sendNotification on the FirebasePluginMessageReceiver class directly. Instead, you must override it using a subclass."); + return false; +} + +@end diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiverManager.h b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiverManager.h new file mode 100644 index 00000000..96dac9d8 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiverManager.h @@ -0,0 +1,6 @@ +#import "FirebasePluginMessageReceiver.h" + +@interface FirebasePluginMessageReceiverManager ++ (void) register:(FirebasePluginMessageReceiver *)receiver; ++ (bool) sendNotification:(NSDictionary *)userInfo; +@end diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiverManager.m b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiverManager.m new file mode 100644 index 00000000..7d463265 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/FirebasePluginMessageReceiverManager.m @@ -0,0 +1,24 @@ +#import "FirebasePluginMessageReceiverManager.h" + +@implementation FirebasePluginMessageReceiverManager + +static NSMutableArray* receivers; + ++ (void) register:(FirebasePluginMessageReceiver*)receiver { + if(receivers == nil){ + receivers = [[NSMutableArray alloc] init]; + } + [receivers addObject:receiver]; +} + ++ (bool) sendNotification:(NSDictionary *)userInfo { + bool handled = false; + for(FirebasePluginMessageReceiver* receiver in receivers){ + bool wasHandled = [receiver sendNotification:userInfo]; + if(wasHandled){ + handled = true; + } + } + return handled; +} +@end diff --git a/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/GoogleService-Info.plist b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/GoogleService-Info.plist new file mode 100644 index 00000000..5516ebf3 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-firebasex/src/ios/GoogleService-Info.plist @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file -- cgit v1.2.3-70-g09d2