diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-08-31 23:07:20 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-08-31 23:07:20 +0200 |
| commit | 22721a013bdd10d5eb395ba18453585f5f3f1f7f (patch) | |
| tree | 5a920e31d6026ed5dc55265e5fd057febccc50e3 /StoneIsland/plugins/cordova-plugin-firebasex/src | |
| parent | d22d51a1ae49680015326857360eb699f31efced (diff) | |
rebuild the ios platform and the plugins
Diffstat (limited to 'StoneIsland/plugins/cordova-plugin-firebasex/src')
18 files changed, 5497 insertions, 0 deletions
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<Bundle> 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<String, AuthCredential> authCredentials = new HashMap<String, AuthCredential>(); + private Map<String, OAuthProvider> authProviders = new HashMap<String, OAuthProvider>(); + + @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<Bundle>(); + } + 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<GoogleSignInAccount> 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<Bundle>(); + } + 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<String> 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<Void> 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<String, Object> defaultsToMap(JSONObject object) throws JSONException { + final Map<String, Object> map = new HashMap<String, Object>(); + + for (Iterator<String> 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<Void>() { + @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<GetTokenResult>() { + @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<AuthResult> 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<String, Trace> traces = new HashMap<String, Trace>(); + + 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<NotificationChannel> 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<NotificationChannel> listChannels(){ + List<NotificationChannel> 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<NotificationChannel> 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<DocumentReference>() { + @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<Void>() { + @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<Void>() { + @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<Void>() { + @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<DocumentSnapshot>() { + @Override + public void onComplete(@NonNull Task<DocumentSnapshot> 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<DocumentSnapshot>() { + @Override + public void onComplete(@NonNull Task<DocumentSnapshot> 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<QuerySnapshot>() { + @Override + public void onComplete(@NonNull Task<QuerySnapshot> 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<Void> task, CallbackContext callbackContext) { + try { + task.addOnCompleteListener(new OnCompleteListener<Void>() { + @Override + public void onComplete(@NonNull Task<Void> 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<AuthResult> 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<AuthResult> { + @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<AuthResult> { + private final CallbackContext callbackContext; + + public AuthResultOnCompleteListener(CallbackContext callbackContext) { + this.callbackContext = callbackContext; + } + + @Override + public void onComplete(@NonNull Task<AuthResult> 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<String, Object> jsonStringToMap(String jsonString) throws JSONException { + Type type = new TypeToken<Map<String, Object>>(){}.getType(); + return gson.fromJson(jsonString, type); + } + + + private JSONObject mapToJsonObject(Map<String, Object> 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<FirebasePluginMessageReceiver> receivers = new ArrayList<FirebasePluginMessageReceiver>(); + + 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<String, String> 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<String, String> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> +</resources> 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 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string name="default_notification_channel_id">fcm_default_channel</string> + <string name="default_notification_channel_name">Default</string> +</resources>
\ 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 <GoogleSignIn/GoogleSignIn.h>
+
+@import UserNotifications;
+@import AuthenticationServices;
+
+@interface AppDelegate (FirebasePlugin) <UIApplicationDelegate, GIDSignInDelegate, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
++ (AppDelegate *) instance;
+@property (nonatomic, strong) NSNumber * _Nonnull applicationInBackground;
+@property (NS_NONATOMIC_IOSONLY, nullable, weak) id <UNUserNotificationCenterDelegate> 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 <objc/runtime.h> + + +@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 () <UNUserNotificationCenterDelegate, FIRMessagingDelegate> +@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 <Cordova/CDV.h> +#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 <Cordova/CDV.h> +#import "AppDelegate.h" +#import <GoogleSignIn/GoogleSignIn.h> +@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<UNUserNotificationCenterDelegate> _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 <Foundation/Foundation.h> + +@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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> +</dict> +</plist>
\ No newline at end of file |
