diff --git a/android/app/build.gradle b/android/app/build.gradle index 4b4fad1912e..b002fd81eb5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -93,7 +93,6 @@ android { versionName "4.68.0" vectorDrawables.useSupportLibrary = true manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] - missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60" // See note below! resValue "string", "rn_config_reader_custom_package", "chat.rocket.reactnative" } @@ -144,7 +143,6 @@ dependencies { implementation jscFlavor } - implementation project(':react-native-notifications') implementation "com.google.firebase:firebase-messaging:23.3.1" implementation project(':watermelondb-jsi') diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 552191d1316..4fb70ab1a89 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -75,6 +75,14 @@ + + + + + + + + + + + + diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt index e2ed12f7eab..502ae26784f 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt @@ -7,8 +7,11 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate import android.os.Bundle import com.zoontek.rnbootsplash.RNBootSplash -import android.content.Intent; -import android.content.res.Configuration; +import android.content.Intent +import android.content.res.Configuration +import chat.rocket.reactnative.notification.VideoConfModule +import chat.rocket.reactnative.notification.VideoConfNotification +import com.google.gson.GsonBuilder class MainActivity : ReactActivity() { @@ -25,9 +28,60 @@ class MainActivity : ReactActivity() { override fun createReactActivityDelegate(): ReactActivityDelegate = DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) - override fun onCreate(savedInstanceState: Bundle?) { + override fun onCreate(savedInstanceState: Bundle?) { RNBootSplash.init(this, R.style.BootTheme) super.onCreate(null) + + // Handle video conf action from notification + intent?.let { handleVideoConfIntent(it) } + } + + public override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + // Handle video conf action when activity is already running + handleVideoConfIntent(intent) + } + + private fun handleVideoConfIntent(intent: Intent) { + if (intent.getBooleanExtra("videoConfAction", false)) { + val notificationId = intent.getIntExtra("notificationId", 0) + val event = intent.getStringExtra("event") ?: return + val rid = intent.getStringExtra("rid") ?: "" + val callerId = intent.getStringExtra("callerId") ?: "" + val callerName = intent.getStringExtra("callerName") ?: "" + val host = intent.getStringExtra("host") ?: "" + val callId = intent.getStringExtra("callId") ?: "" + + android.util.Log.d("RocketChat.MainActivity", "Handling video conf intent - event: $event, rid: $rid, host: $host, callId: $callId") + + // Cancel the notification + if (notificationId != 0) { + VideoConfNotification.cancelById(this, notificationId) + } + + // Store action for JS to pick up - include all required fields + val data = mapOf( + "notificationType" to "videoconf", + "rid" to rid, + "event" to event, + "host" to host, + "callId" to callId, + "caller" to mapOf( + "_id" to callerId, + "name" to callerName + ) + ) + + val gson = GsonBuilder().create() + val jsonData = gson.toJson(data) + + android.util.Log.d("RocketChat.MainActivity", "Storing video conf action: $jsonData") + + VideoConfModule.storePendingAction(this, jsonData) + + // Clear the video conf flag to prevent re-processing + intent.removeExtra("videoConfAction") + } } override fun invokeDefaultOnBackPressed() { diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt index 28958176b81..89622d5d0a4 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt @@ -1,9 +1,7 @@ package chat.rocket.reactnative import android.app.Application -import android.content.Context import android.content.res.Configuration -import android.os.Bundle import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactHost @@ -18,19 +16,27 @@ import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.react.soloader.OpenSourceMergedSoMapping import com.facebook.soloader.SoLoader import com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage; -import com.wix.reactnativenotifications.core.AppLaunchHelper -import com.wix.reactnativenotifications.core.AppLifecycleFacade -import com.wix.reactnativenotifications.core.JsIOHelper -import com.wix.reactnativenotifications.core.notification.INotificationsApplication -import com.wix.reactnativenotifications.core.notification.IPushNotification import com.bugsnag.android.Bugsnag import expo.modules.ApplicationLifecycleDispatcher import chat.rocket.reactnative.networking.SSLPinningTurboPackage; import chat.rocket.reactnative.storage.MMKVKeyManager; import chat.rocket.reactnative.storage.SecureStoragePackage; import chat.rocket.reactnative.notification.CustomPushNotification; +import chat.rocket.reactnative.notification.VideoConfTurboPackage -open class MainApplication : Application(), ReactApplication, INotificationsApplication { +/** + * Main Application class. + * + * NOTIFICATION ARCHITECTURE: + * - JS layer uses expo-notifications for token registration and event handling + * - Native layer uses RCFirebaseMessagingService + CustomPushNotification for: + * - FCM message handling + * - Notification display with MessagingStyle + * - E2E encrypted message decryption + * - Direct reply functionality + * - Message-id-only notification loading + */ +open class MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) { @@ -38,6 +44,7 @@ open class MainApplication : Application(), ReactApplication, INotificationsAppl PackageList(this).packages.apply { add(SSLPinningTurboPackage()) add(WatermelonDBJSIPackage()) + add(VideoConfTurboPackage()) add(SecureStoragePackage()) } @@ -78,19 +85,4 @@ open class MainApplication : Application(), ReactApplication, INotificationsAppl super.onConfigurationChanged(newConfig) ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) } - - override fun getPushNotification( - context: Context, - bundle: Bundle, - defaultFacade: AppLifecycleFacade, - defaultAppLaunchHelper: AppLaunchHelper - ): IPushNotification { - return CustomPushNotification( - context, - bundle, - defaultFacade, - defaultAppLaunchHelper, - JsIOHelper() - ) - } } diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java b/android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java index caf90dc7ca1..cad28734ea4 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java @@ -1,7 +1,5 @@ package chat.rocket.reactnative.notification; -import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME; - import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -25,10 +23,6 @@ import com.bumptech.glide.request.RequestOptions; import com.facebook.react.bridge.ReactApplicationContext; import com.google.gson.Gson; -import com.wix.reactnativenotifications.core.AppLaunchHelper; -import com.wix.reactnativenotifications.core.AppLifecycleFacade; -import com.wix.reactnativenotifications.core.JsIOHelper; -import com.wix.reactnativenotifications.core.notification.PushNotification; import java.util.ArrayList; import java.util.Date; @@ -37,17 +31,20 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import chat.rocket.reactnative.BuildConfig; +import chat.rocket.reactnative.MainActivity; import chat.rocket.reactnative.R; /** * Custom push notification handler for Rocket.Chat. * * Handles standard push notifications and End-to-End encrypted (E2E) notifications. - * For E2E notifications, waits for React Native initialization before decrypting and displaying. + * Provides MessagingStyle notifications, direct reply, and advanced processing. */ -public class CustomPushNotification extends PushNotification { +public class CustomPushNotification { private static final String TAG = "RocketChat.CustomPush"; private static final boolean ENABLE_VERBOSE_LOGS = BuildConfig.DEBUG; @@ -59,14 +56,21 @@ public class CustomPushNotification extends PushNotification { // Constants public static final String KEY_REPLY = "KEY_REPLY"; public static final String NOTIFICATION_ID = "NOTIFICATION_ID"; + private static final String CHANNEL_ID = "rocketchatrn_channel_01"; + private static final String CHANNEL_NAME = "All"; // Instance fields - final NotificationManager notificationManager; + private final Context mContext; + private Bundle mBundle; + private final NotificationManager notificationManager; - public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, - AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) { - super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper); - notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + public CustomPushNotification(Context context, Bundle bundle) { + this.mContext = context; + this.mBundle = bundle; + this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + // Ensure notification channel exists + createNotificationChannel(); } /** @@ -80,59 +84,66 @@ public static void setReactContext(ReactApplicationContext context) { public static void clearMessages(int notId) { notificationMessages.remove(Integer.toString(notId)); } + + /** + * Check if React Native is initialized + */ + private boolean isReactInitialized() { + return reactApplicationContext != null; + } - @Override - public void onReceived() throws InvalidNotificationException { - Bundle bundle = mNotificationProps.asBundle(); - String notId = bundle.getString("notId"); + public void onReceived() { + String notId = mBundle.getString("notId"); if (notId == null || notId.isEmpty()) { - throw new InvalidNotificationException("Missing notification ID"); + Log.w(TAG, "Missing notification ID, ignoring notification"); + return; } try { Integer.parseInt(notId); } catch (NumberFormatException e) { - throw new InvalidNotificationException("Invalid notification ID format: " + notId); + Log.w(TAG, "Invalid notification ID format: " + notId); + return; } // Check if React is ready - needed for MMKV access (avatars, encryption, message-id-only) - if (!mAppLifecycleFacade.isReactInitialized()) { - android.util.Log.w(TAG, "React not initialized yet, waiting before processing notification..."); + if (!isReactInitialized()) { + Log.w(TAG, "React not initialized yet, waiting before processing notification..."); // Wait for React to initialize with timeout new Thread(() -> { int attempts = 0; int maxAttempts = 50; // 5 seconds total (50 * 100ms) - while (!mAppLifecycleFacade.isReactInitialized() && attempts < maxAttempts) { + while (!isReactInitialized() && attempts < maxAttempts) { try { Thread.sleep(100); // Wait 100ms attempts++; if (attempts % 10 == 0 && ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "Still waiting for React initialization... (" + (attempts * 100) + "ms elapsed)"); + Log.d(TAG, "Still waiting for React initialization... (" + (attempts * 100) + "ms elapsed)"); } } catch (InterruptedException e) { - android.util.Log.e(TAG, "Wait interrupted", e); + Log.e(TAG, "Wait interrupted", e); Thread.currentThread().interrupt(); return; } } - if (mAppLifecycleFacade.isReactInitialized()) { - android.util.Log.i(TAG, "React initialized after " + (attempts * 100) + "ms, proceeding with notification"); + if (isReactInitialized()) { + Log.i(TAG, "React initialized after " + (attempts * 100) + "ms, proceeding with notification"); try { handleNotification(); } catch (Exception e) { - android.util.Log.e(TAG, "Failed to process notification after React initialization", e); + Log.e(TAG, "Failed to process notification after React initialization", e); } } else { - android.util.Log.e(TAG, "Timeout waiting for React initialization after " + (maxAttempts * 100) + "ms, processing without MMKV"); + Log.e(TAG, "Timeout waiting for React initialization after " + (maxAttempts * 100) + "ms, processing without MMKV"); try { handleNotification(); } catch (Exception e) { - android.util.Log.e(TAG, "Failed to process notification without React context", e); + Log.e(TAG, "Failed to process notification without React context", e); } } }).start(); @@ -141,23 +152,21 @@ public void onReceived() throws InvalidNotificationException { } if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "React already initialized, proceeding with notification"); + Log.d(TAG, "React already initialized, proceeding with notification"); } try { handleNotification(); } catch (Exception e) { - android.util.Log.e(TAG, "Failed to process notification on main thread", e); - throw new InvalidNotificationException("Notification processing failed: " + e.getMessage()); + Log.e(TAG, "Failed to process notification on main thread", e); } } private void handleNotification() { - Bundle received = mNotificationProps.asBundle(); - Ejson receivedEjson = safeFromJson(received.getString("ejson", "{}"), Ejson.class); + Ejson receivedEjson = safeFromJson(mBundle.getString("ejson", "{}"), Ejson.class); if (receivedEjson != null && receivedEjson.notificationType != null && receivedEjson.notificationType.equals("message-id-only")) { - android.util.Log.d(TAG, "Detected message-id-only notification, will fetch full content from server"); + Log.d(TAG, "Detected message-id-only notification, will fetch full content from server"); loadNotificationAndProcess(receivedEjson); return; // Exit early, notification will be processed in callback } @@ -171,30 +180,19 @@ private void loadNotificationAndProcess(Ejson ejson) { @Override public void call(@Nullable Bundle bundle) { if (bundle != null) { - android.util.Log.d(TAG, "Successfully loaded notification content from server, updating notification props"); + Log.d(TAG, "Successfully loaded notification content from server, updating notification props"); if (ENABLE_VERBOSE_LOGS) { - // BEFORE createProps - android.util.Log.d(TAG, "[BEFORE createProps] bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false)); - android.util.Log.d(TAG, "[BEFORE createProps] bundle.title=" + (bundle.getString("title") != null ? "[present]" : "[null]")); - android.util.Log.d(TAG, "[BEFORE createProps] bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0)); - android.util.Log.d(TAG, "[BEFORE createProps] bundle has ejson=" + (bundle.getString("ejson") != null)); + Log.d(TAG, "[BEFORE update] bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false)); + Log.d(TAG, "[BEFORE update] bundle.title=" + (bundle.getString("title") != null ? "[present]" : "[null]")); + Log.d(TAG, "[BEFORE update] bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0)); } synchronized(CustomPushNotification.this) { - mNotificationProps = createProps(bundle); - } - - if (ENABLE_VERBOSE_LOGS) { - // AFTER createProps - verify it worked - Bundle verifyBundle = mNotificationProps.asBundle(); - android.util.Log.d(TAG, "[AFTER createProps] mNotificationProps.notificationLoaded=" + verifyBundle.getBoolean("notificationLoaded", false)); - android.util.Log.d(TAG, "[AFTER createProps] mNotificationProps.title=" + (verifyBundle.getString("title") != null ? "[present]" : "[null]")); - android.util.Log.d(TAG, "[AFTER createProps] mNotificationProps.message length=" + (verifyBundle.getString("message") != null ? verifyBundle.getString("message").length() : 0)); - android.util.Log.d(TAG, "[AFTER createProps] mNotificationProps has ejson=" + (verifyBundle.getString("ejson") != null)); + mBundle = bundle; } } else { - android.util.Log.w(TAG, "Failed to load notification content from server, will display placeholder notification"); + Log.w(TAG, "Failed to load notification content from server, will display placeholder notification"); } processNotification(); @@ -203,28 +201,26 @@ public void call(@Nullable Bundle bundle) { } private void processNotification() { - // We should re-read these values since that can be changed by notificationLoad - Bundle bundle = mNotificationProps.asBundle(); - Ejson loadedEjson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class); - String notId = bundle.getString("notId", "1"); + Ejson loadedEjson = safeFromJson(mBundle.getString("ejson", "{}"), Ejson.class); + String notId = mBundle.getString("notId", "1"); if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "[processNotification] notId=" + notId); - android.util.Log.d(TAG, "[processNotification] bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false)); - android.util.Log.d(TAG, "[processNotification] bundle.title=" + (bundle.getString("title") != null ? "[present]" : "[null]")); - android.util.Log.d(TAG, "[processNotification] bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0)); - android.util.Log.d(TAG, "[processNotification] loadedEjson.notificationType=" + (loadedEjson != null ? loadedEjson.notificationType : "null")); - android.util.Log.d(TAG, "[processNotification] loadedEjson.sender=" + (loadedEjson != null && loadedEjson.sender != null ? loadedEjson.sender.username : "null")); + Log.d(TAG, "[processNotification] notId=" + notId); + Log.d(TAG, "[processNotification] bundle.notificationLoaded=" + mBundle.getBoolean("notificationLoaded", false)); + Log.d(TAG, "[processNotification] bundle.title=" + (mBundle.getString("title") != null ? "[present]" : "[null]")); + Log.d(TAG, "[processNotification] bundle.message length=" + (mBundle.getString("message") != null ? mBundle.getString("message").length() : 0)); + Log.d(TAG, "[processNotification] loadedEjson.notificationType=" + (loadedEjson != null ? loadedEjson.notificationType : "null")); + Log.d(TAG, "[processNotification] loadedEjson.sender=" + (loadedEjson != null && loadedEjson.sender != null ? loadedEjson.sender.username : "null")); } // Handle E2E encrypted notifications if (isE2ENotification(loadedEjson)) { - handleE2ENotification(bundle, loadedEjson, notId); + handleE2ENotification(mBundle, loadedEjson, notId); return; // E2E processor will handle showing the notification } // Handle regular (non-E2E) notifications - showNotification(bundle, loadedEjson, notId); + showNotification(mBundle, loadedEjson, notId); } /** @@ -245,8 +241,7 @@ private void handleE2ENotification(Bundle bundle, Ejson ejson, String notId) { if (decrypted != null) { bundle.putString("message", decrypted); - mNotificationProps = createProps(bundle); - bundle = mNotificationProps.asBundle(); + mBundle = bundle; ejson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class); showNotification(bundle, ejson, notId); } else { @@ -266,11 +261,9 @@ private void handleE2ENotification(Bundle bundle, Ejson ejson, String notId) { new E2ENotificationProcessor.NotificationCallback() { @Override public void onDecryptionComplete(Bundle decryptedBundle, Ejson decryptedEjson, String notificationId) { - // Update props and show notification - mNotificationProps = createProps(decryptedBundle); - Bundle finalBundle = mNotificationProps.asBundle(); - Ejson finalEjson = safeFromJson(finalBundle.getString("ejson", "{}"), Ejson.class); - showNotification(finalBundle, finalEjson, notificationId); + mBundle = decryptedBundle; + Ejson finalEjson = safeFromJson(decryptedBundle.getString("ejson", "{}"), Ejson.class); + showNotification(decryptedBundle, finalEjson, notificationId); } @Override @@ -308,154 +301,189 @@ private void showNotification(Bundle bundle, Ejson ejson, String notId) { String avatarUri = ejson != null ? ejson.getAvatarUri() : null; if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "[showNotification] avatarUri=" + (avatarUri != null ? "[present]" : "[null]")); + Log.d(TAG, "[showNotification] avatarUri=" + (avatarUri != null ? "[present]" : "[null]")); } bundle.putString("avatarUri", avatarUri); // Handle special notification types - if (ejson != null && ejson.notificationType instanceof String && - ejson.notificationType.equals("videoconf")) { - notifyReceivedToJS(); + if (ejson != null && "videoconf".equals(ejson.notificationType)) { + handleVideoConfNotification(bundle, ejson); + return; } else { // Show regular notification if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "[Before add to notificationMessages] notId=" + notId + ", bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0) + ", bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false)); + Log.d(TAG, "[Before add to notificationMessages] notId=" + notId + ", bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0) + ", bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false)); } notificationMessages.get(notId).add(bundle); if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "[After add] notificationMessages[" + notId + "].size=" + notificationMessages.get(notId).size()); + Log.d(TAG, "[After add] notificationMessages[" + notId + "].size=" + notificationMessages.get(notId).size()); } postNotification(Integer.parseInt(notId)); - notifyReceivedToJS(); } } - @Override - public void onOpened() { - Bundle bundle = mNotificationProps.asBundle(); - final String notId = bundle.getString("notId", "1"); - notificationMessages.remove(notId); - digestNotification(); + /** + * Handles video conference notifications. + * Shows incoming call notification or cancels existing one based on status. + */ + private void handleVideoConfNotification(Bundle bundle, Ejson ejson) { + VideoConfNotification videoConf = new VideoConfNotification(mContext); + + Integer status = ejson.status; + String rid = ejson.rid; + // Video conf uses 'caller' field, regular messages use 'sender' + String callerId = ""; + if (ejson.caller != null && ejson.caller._id != null) { + callerId = ejson.caller._id; + } else if (ejson.sender != null && ejson.sender._id != null) { + callerId = ejson.sender._id; + } + + Log.d(TAG, "Video conf notification - status: " + status + ", rid: " + rid); + + if (status == null || status == 0) { + // Incoming call - show notification + videoConf.showIncomingCall(bundle, ejson); + } else if (status == 4) { + // Call cancelled/ended - dismiss notification + videoConf.cancelCall(rid, callerId); + } else { + Log.d(TAG, "Unknown video conf status: " + status); + } } - @Override - protected Notification.Builder getNotificationBuilder(PendingIntent intent) { - final Notification.Builder notification = new Notification.Builder(mContext); + private void postNotification(int notificationId) { + Notification.Builder notification = buildNotification(notificationId); + if (notification != null && notificationManager != null) { + notificationManager.notify(notificationId, notification.build()); + } + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ); + if (notificationManager != null) { + notificationManager.createNotificationChannel(channel); + } + } + } - Bundle bundle = mNotificationProps.asBundle(); - String notId = bundle.getString("notId", "1"); - String title = bundle.getString("title"); - String message = bundle.getString("message"); - Boolean notificationLoaded = bundle.getBoolean("notificationLoaded", false); - Ejson ejson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class); + private Notification.Builder buildNotification(int notificationId) { + String notId = Integer.toString(notificationId); + String title = mBundle.getString("title"); + String message = mBundle.getString("message"); + Boolean notificationLoaded = mBundle.getBoolean("notificationLoaded", false); + Ejson ejson = safeFromJson(mBundle.getString("ejson", "{}"), Ejson.class); if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "[getNotificationBuilder] notId=" + notId); - android.util.Log.d(TAG, "[getNotificationBuilder] notificationLoaded=" + notificationLoaded); - android.util.Log.d(TAG, "[getNotificationBuilder] title=" + (title != null ? "[present]" : "[null]")); - android.util.Log.d(TAG, "[getNotificationBuilder] message length=" + (message != null ? message.length() : 0)); - android.util.Log.d(TAG, "[getNotificationBuilder] ejson=" + (ejson != null ? "present" : "null")); - android.util.Log.d(TAG, "[getNotificationBuilder] ejson.notificationType=" + (ejson != null ? ejson.notificationType : "null")); - android.util.Log.d(TAG, "[getNotificationBuilder] ejson.sender=" + (ejson != null && ejson.sender != null ? ejson.sender.username : "null")); + Log.d(TAG, "[buildNotification] notId=" + notId); + Log.d(TAG, "[buildNotification] notificationLoaded=" + notificationLoaded); + Log.d(TAG, "[buildNotification] title=" + (title != null ? "[present]" : "[null]")); + Log.d(TAG, "[buildNotification] message length=" + (message != null ? message.length() : 0)); + } + + // Create pending intent to open the app + Intent intent = new Intent(mContext, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtras(mBundle); + + PendingIntent pendingIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + pendingIntent = PendingIntent.getActivity(mContext, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + pendingIntent = PendingIntent.getActivity(mContext, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + Notification.Builder notification; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notification = new Notification.Builder(mContext, CHANNEL_ID); + } else { + notification = new Notification.Builder(mContext); } notification .setContentTitle(title) .setContentText(message) - .setContentIntent(intent) + .setContentIntent(pendingIntent) .setPriority(Notification.PRIORITY_HIGH) .setDefaults(Notification.DEFAULT_ALL) .setAutoCancel(true); - Integer notificationId = Integer.parseInt(notId); notificationColor(notification); - notificationChannel(notification); - notificationIcons(notification, bundle); + notificationIcons(notification, mBundle); notificationDismiss(notification, notificationId); // if notificationType is null (RC < 3.5) or notificationType is different of message-id-only or notification was loaded successfully if (ejson == null || ejson.notificationType == null || !ejson.notificationType.equals("message-id-only") || notificationLoaded) { - android.util.Log.i(TAG, "[getNotificationBuilder] ✅ Rendering FULL notification style (ejson=" + (ejson != null) + ", notificationType=" + (ejson != null ? ejson.notificationType : "null") + ", notificationLoaded=" + notificationLoaded + ")"); - notificationStyle(notification, notificationId, bundle); - notificationReply(notification, notificationId, bundle); - - // message couldn't be loaded from server (Fallback notification) + Log.i(TAG, "[buildNotification] ✅ Rendering FULL notification style"); + notificationStyle(notification, notificationId, mBundle); + notificationReply(notification, notificationId, mBundle); } else { - android.util.Log.w(TAG, "[getNotificationBuilder] ⚠️ Rendering FALLBACK notification (ejson=" + (ejson != null) + ", notificationType=" + (ejson != null ? ejson.notificationType : "null") + ", notificationLoaded=" + notificationLoaded + ")"); - // iterate over the current notification ids to dismiss fallback notifications from same server - for (Map.Entry> bundleList : notificationMessages.entrySet()) { - // iterate over the notifications with this id (same host + rid) - Iterator iterator = bundleList.getValue().iterator(); - while (iterator.hasNext()) { - Bundle not = (Bundle) iterator.next(); - // get the notification info - Ejson notEjson = safeFromJson(not.getString("ejson", "{}"), Ejson.class); - // if already has a notification from same server - if (ejson != null && notEjson != null && ejson.serverURL().equals(notEjson.serverURL())) { - String id = not.getString("notId"); - // cancel this notification - if (notificationManager != null) { - try { - notificationManager.cancel(Integer.parseInt(id)); - if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "Cancelled previous fallback notification from same server"); - } - } catch (NumberFormatException e) { - android.util.Log.e(TAG, "Invalid notification ID for cancel: " + id, e); + Log.w(TAG, "[buildNotification] ⚠️ Rendering FALLBACK notification"); + // Cancel previous fallback notifications from same server + cancelPreviousFallbackNotifications(ejson); + } + + return notification; + } + + private void cancelPreviousFallbackNotifications(Ejson ejson) { + for (Map.Entry> bundleList : notificationMessages.entrySet()) { + Iterator iterator = bundleList.getValue().iterator(); + while (iterator.hasNext()) { + Bundle not = iterator.next(); + Ejson notEjson = safeFromJson(not.getString("ejson", "{}"), Ejson.class); + if (ejson != null && notEjson != null && ejson.serverURL().equals(notEjson.serverURL())) { + String id = not.getString("notId"); + if (notificationManager != null && id != null) { + try { + notificationManager.cancel(Integer.parseInt(id)); + if (ENABLE_VERBOSE_LOGS) { + Log.d(TAG, "Cancelled previous fallback notification from same server"); } + } catch (NumberFormatException e) { + Log.e(TAG, "Invalid notification ID for cancel: " + id, e); } } } } } - - return notification; - } - - private void notifyReceivedToJS() { - boolean isReactInitialized = mAppLifecycleFacade.isReactInitialized(); - if (isReactInitialized) { - mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext()); - } } private Bitmap getAvatar(String uri) { if (uri == null || uri.isEmpty()) { if (ENABLE_VERBOSE_LOGS) { - android.util.Log.w(TAG, "getAvatar called with null/empty URI"); + Log.w(TAG, "getAvatar called with null/empty URI"); } return largeIcon(); } - // Sanitize URL for logging (remove query params with tokens) - String sanitizedUri = uri; - int queryStart = uri.indexOf("?"); - if (queryStart != -1) { - sanitizedUri = uri.substring(0, queryStart) + "?[auth_params]"; - } - if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "Fetching avatar from: " + sanitizedUri); + String sanitizedUri = uri; + int queryStart = uri.indexOf("?"); + if (queryStart != -1) { + sanitizedUri = uri.substring(0, queryStart) + "?[auth_params]"; + } + Log.d(TAG, "Fetching avatar from: " + sanitizedUri); } try { + // Use a 3-second timeout to avoid blocking the FCM service for too long + // FCM has a 10-second limit, so we need to fail fast and use fallback icon Bitmap avatar = Glide.with(mContext) .asBitmap() .apply(RequestOptions.bitmapTransform(new RoundedCorners(10))) .load(uri) .submit(100, 100) - .get(); + .get(3, TimeUnit.SECONDS); - if (avatar != null) { - if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "Successfully loaded avatar"); - } - } else { - android.util.Log.w(TAG, "Avatar loaded but is null"); - } return avatar != null ? avatar : largeIcon(); - } catch (final ExecutionException | InterruptedException e) { - android.util.Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e); + } catch (final ExecutionException | InterruptedException | TimeoutException e) { + Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e); return largeIcon(); } } @@ -464,16 +492,13 @@ private Bitmap largeIcon() { final Resources res = mContext.getResources(); String packageName = mContext.getPackageName(); int largeIconResId = res.getIdentifier("ic_notification", "drawable", packageName); - Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId); - return largeIconBitmap; + return BitmapFactory.decodeResource(res, largeIconResId); } private void notificationIcons(Notification.Builder notification, Bundle bundle) { final Resources res = mContext.getResources(); String packageName = mContext.getPackageName(); - int smallIconResId = res.getIdentifier("ic_notification", "drawable", packageName); - Ejson ejson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class); notification.setSmallIcon(smallIconResId); @@ -486,28 +511,6 @@ private void notificationIcons(Notification.Builder notification, Bundle bundle) } } - private void notificationChannel(Notification.Builder notification) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - String CHANNEL_ID = "rocketchatrn_channel_01"; - String CHANNEL_NAME = "All"; - - // User-visible importance level: Urgent - Makes a sound and appears as a heads-up notification - // https://developer.android.com/training/notify-user/channels#importance - NotificationChannel channel = new NotificationChannel(CHANNEL_ID, - CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH); - - final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager != null) { - notificationManager.createNotificationChannel(channel); - } else { - android.util.Log.e(TAG, "NotificationManager is null, cannot create notification channel"); - } - - notification.setChannelId(CHANNEL_ID); - } - } - private String extractMessage(String message, Ejson ejson) { if (message == null) { return ""; @@ -515,7 +518,7 @@ private String extractMessage(String message, Ejson ejson) { if (ejson != null && ejson.type != null && !ejson.type.equals("d")) { int pos = message.indexOf(":"); int start = pos == -1 ? 0 : pos + 2; - return message.substring(start, message.length()); + return message.substring(start); } return message; } @@ -530,25 +533,17 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun List bundles = notificationMessages.get(Integer.toString(notId)); if (ENABLE_VERBOSE_LOGS) { - android.util.Log.d(TAG, "[notificationStyle] notId=" + notId + ", bundles=" + (bundles != null ? bundles.size() : "null")); - if (bundles != null && bundles.size() > 0) { - Bundle firstBundle = bundles.get(0); - android.util.Log.d(TAG, "[notificationStyle] first bundle.message length=" + (firstBundle.getString("message") != null ? firstBundle.getString("message").length() : 0)); - android.util.Log.d(TAG, "[notificationStyle] first bundle.notificationLoaded=" + firstBundle.getBoolean("notificationLoaded", false)); - } + Log.d(TAG, "[notificationStyle] notId=" + notId + ", bundles=" + (bundles != null ? bundles.size() : "null")); } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { Notification.InboxStyle messageStyle = new Notification.InboxStyle(); if (bundles != null) { - for (int i = 0; i < bundles.size(); i++) { - Bundle data = bundles.get(i); + for (Bundle data : bundles) { String message = data.getString("message"); - messageStyle.addLine(message); } } - notification.setStyle(messageStyle); } else { Notification.MessagingStyle messageStyle; @@ -567,9 +562,7 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun messageStyle.setConversationTitle(title); if (bundles != null) { - for (int i = 0; i < bundles.size(); i++) { - Bundle data = bundles.get(i); - + for (Bundle data : bundles) { long timestamp = data.getLong("time"); String message = data.getString("message"); String senderId = data.getString("senderId"); @@ -582,18 +575,16 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun messageStyle.addMessage(m, timestamp, senderName); } else { Bitmap avatar = getAvatar(avatarUri); - String senderName = ejson != null ? ejson.senderName : "Unknown"; - Person.Builder sender = new Person.Builder() + Person.Builder senderBuilder = new Person.Builder() .setKey(senderId) .setName(senderName); if (avatar != null) { - sender.setIcon(Icon.createWithBitmap(avatar)); + senderBuilder.setIcon(Icon.createWithBitmap(avatar)); } - Person person = sender.build(); - + Person person = senderBuilder.build(); messageStyle.addMessage(m, timestamp, person); } } @@ -630,8 +621,7 @@ private void notificationReply(Notification.Builder notification, int notificati .setLabel(label) .build(); - CharSequence title = label; - Notification.Action replyAction = new Notification.Action.Builder(smallIconResId, title, replyPendingIntent) + Notification.Action replyAction = new Notification.Action.Builder(smallIconResId, label, replyPendingIntent) .addRemoteInput(remoteInput) .setAllowGeneratedReplies(true) .build(); @@ -657,11 +647,6 @@ private void notificationLoad(Ejson ejson, Callback callback) { /** * Safely parses JSON string to object with error handling. - * - * @param json JSON string to parse - * @param classOfT Target class type - * @param Type parameter - * @return Parsed object, or null if parsing fails */ private static T safeFromJson(String json, Class classOfT) { if (json == null || json.trim().isEmpty() || json.equals("{}")) { diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java b/android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java index d0b0e541c05..036d03f24f6 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java @@ -34,20 +34,18 @@ public class Ejson { String rid; String type; Sender sender; + Caller caller; // For video conf notifications String messageId; + String callId; // For video conf notifications String notificationType; String messageType; String senderName; String msg; + Integer status; // For video conf: 0=incoming, 4=cancelled + String tmid; Content content; - /** - * Get MMKV instance with encryption. - * MMKV is already initialized in MainApplication.onCreate(). - * Uses the encryption key from MMKVKeyManager which was set at app startup. - * MMKV internally caches instances, so calling this multiple times is efficient. - */ private MMKV getMMKV() { String encryptionKey = MMKVKeyManager.getEncryptionKey(); if (encryptionKey != null && !encryptionKey.isEmpty()) { @@ -193,6 +191,11 @@ static class Sender { String name; } + static class Caller { + String _id; + String name; + } + static class Content { String algorithm; String ciphertext; diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java b/android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java index 1478a39c3ae..9d443630cc9 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java @@ -126,6 +126,7 @@ public void load(final Ejson ejson, Callback callback) { Request request = new Request.Builder() .header("x-user-id", userId) .header("x-auth-token", userToken) + .header("User-Agent", NotificationHelper.getUserAgent()) .url(urlBuilder.addQueryParameter("id", messageId).build()) .build(); diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/NativeVideoConfSpec.kt b/android/app/src/main/java/chat/rocket/reactnative/notification/NativeVideoConfSpec.kt new file mode 100644 index 00000000000..8eaea08cfd4 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/NativeVideoConfSpec.kt @@ -0,0 +1,23 @@ +package chat.rocket.reactnative.notification + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.turbomodule.core.interfaces.TurboModule + +abstract class NativeVideoConfSpec(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext), TurboModule { + + companion object { + const val NAME = "VideoConfModule" + } + + override fun getName(): String = NAME + + @ReactMethod + abstract fun getPendingAction(promise: Promise) + + @ReactMethod + abstract fun clearPendingAction() +} diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java b/android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java index ac2f1256301..3b53a0be419 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java @@ -1,5 +1,7 @@ package chat.rocket.reactnative.notification; +import android.os.Build; + import chat.rocket.reactnative.BuildConfig; /** @@ -23,5 +25,17 @@ public static String sanitizeUrl(String url) { } return "[workspace]"; } + + /** + * Get the User-Agent string for API requests + * Format: RC Mobile; android {systemVersion}; v{appVersion} ({buildNumber}) + * @return User-Agent string + */ + public static String getUserAgent() { + String systemVersion = Build.VERSION.RELEASE; + String appVersion = BuildConfig.VERSION_NAME; + int buildNumber = BuildConfig.VERSION_CODE; + return String.format("RC Mobile; android %s; v%s (%d)", systemVersion, appVersion, buildNumber); + } } diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt b/android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt new file mode 100644 index 00000000000..c8aba96e6b9 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt @@ -0,0 +1,51 @@ +package chat.rocket.reactnative.notification + +import android.os.Bundle +import android.util.Log +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage + +/** + * Custom Firebase Messaging Service for Rocket.Chat. + * + * Handles incoming FCM messages and routes them to CustomPushNotification + * for advanced processing (E2E decryption, MessagingStyle, direct reply, etc.) + */ +class RCFirebaseMessagingService : FirebaseMessagingService() { + + companion object { + private const val TAG = "RocketChat.FCM" + } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { + Log.d(TAG, "FCM message received from: ${remoteMessage.from}") + + val data = remoteMessage.data + if (data.isEmpty()) { + Log.w(TAG, "FCM message has no data payload, ignoring") + return + } + + // Convert FCM data to Bundle for processing + val bundle = Bundle().apply { + data.forEach { (key, value) -> + putString(key, value) + } + } + + // Process the notification + try { + val notification = CustomPushNotification(this, bundle) + notification.onReceived() + } catch (e: Exception) { + Log.e(TAG, "Error processing FCM message", e) + } + } + + override fun onNewToken(token: String) { + Log.d(TAG, "FCM token refreshed") + // Token handling is done by expo-notifications JS layer + // which uses getDevicePushTokenAsync() + super.onNewToken(token) + } +} diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java b/android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java index 8ee23b64db6..68568ac24bd 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java @@ -27,8 +27,6 @@ import okhttp3.RequestBody; import okhttp3.Response; -import com.wix.reactnativenotifications.core.NotificationIntentAdapter; - public class ReplyBroadcast extends BroadcastReceiver { private Context mContext; private Bundle bundle; @@ -43,7 +41,14 @@ public void onReceive(Context context, Intent intent) { } mContext = context; - bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent); + // Extract bundle directly from intent extras + bundle = intent.getBundleExtra("pushNotification"); + if (bundle == null) { + bundle = intent.getExtras(); + } + if (bundle == null) { + return; + } notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); String notId = bundle.getString("notId"); @@ -79,6 +84,7 @@ protected void replyToMessage(final Ejson ejson, final int notId, final CharSequ Request request = new Request.Builder() .header("x-auth-token", ejson.token()) .header("x-user-id", ejson.userId()) + .header("User-Agent", NotificationHelper.getUserAgent()) .url(String.format("%s/api/v1/chat.sendMessage", serverURL)) .post(body) .build(); diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.kt b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.kt new file mode 100644 index 00000000000..d5aa014fe8c --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.kt @@ -0,0 +1,73 @@ +package chat.rocket.reactnative.notification + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import chat.rocket.reactnative.MainActivity +import com.google.gson.GsonBuilder + +/** + * Handles video conference notification actions (accept/decline). + * Stores the action for the JS layer to process when the app opens. + */ +class VideoConfBroadcast : BroadcastReceiver() { + + companion object { + private const val TAG = "RocketChat.VideoConf" + } + + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action + val extras = intent.extras + + if (action == null || extras == null) { + Log.w(TAG, "Received broadcast with null action or extras") + return + } + + Log.d(TAG, "Received video conf action: $action") + + val event = when (action) { + VideoConfNotification.ACTION_ACCEPT -> "accept" + VideoConfNotification.ACTION_DECLINE -> "decline" + else -> { + Log.w(TAG, "Unknown action: $action") + return + } + } + + // Cancel the notification + val notificationId = extras.getInt("notificationId", 0) + if (notificationId != 0) { + VideoConfNotification.cancelById(context, notificationId) + } + + // Build data for JS layer + val data = mapOf( + "notificationType" to (extras.getString("notificationType") ?: "videoconf"), + "rid" to (extras.getString("rid") ?: ""), + "event" to event, + "caller" to mapOf( + "_id" to (extras.getString("callerId") ?: ""), + "name" to (extras.getString("callerName") ?: "") + ) + ) + + // Store action for the JS layer to pick up + val gson = GsonBuilder().create() + val jsonData = gson.toJson(data) + + VideoConfModule.storePendingAction(context, jsonData) + + Log.d(TAG, "Stored video conf action: $event for rid: ${extras.getString("rid")}") + + // Launch the app + val launchIntent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + putExtras(extras) + putExtra("event", event) + } + context.startActivity(launchIntent) + } +} diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.kt b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.kt new file mode 100644 index 00000000000..d527a686434 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.kt @@ -0,0 +1,104 @@ +package chat.rocket.reactnative.notification + +import android.content.Context +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.modules.core.DeviceEventManagerModule +import java.lang.ref.WeakReference + +/** + * Native module to expose video conference notification actions to JavaScript. + * Used to retrieve pending video conf actions when the app opens. + */ +class VideoConfModule(reactContext: ReactApplicationContext) : NativeVideoConfSpec(reactContext) { + + companion object { + private const val PREFS_NAME = "RocketChatPrefs" + private const val KEY_VIDEO_CONF_ACTION = "videoConfAction" + private const val EVENT_VIDEO_CONF_ACTION = "VideoConfAction" + + private var reactContextRef: WeakReference? = null + + /** + * Sets the React context reference for event emission. + */ + @JvmStatic + fun setReactContext(context: ReactApplicationContext) { + reactContextRef = WeakReference(context) + } + + /** + * Stores a video conference action and emits event to JS if app is running. + * Called from native code when user interacts with video conf notification. + */ + @JvmStatic + fun storePendingAction(context: Context, actionJson: String) { + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + .edit() + .putString(KEY_VIDEO_CONF_ACTION, actionJson) + .apply() + + // Emit event to JS if React context is available (app is running) + emitVideoConfActionEvent(actionJson) + } + + /** + * Emits a video conf action event to JavaScript. + */ + private fun emitVideoConfActionEvent(actionJson: String) { + try { + reactContextRef?.get()?.let { context -> + if (context.hasActiveReactInstance()) { + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit(EVENT_VIDEO_CONF_ACTION, actionJson) + } + } + } catch (e: Exception) { + // Ignore - React context not available + } + } + } + + init { + // Store reference for event emission + setReactContext(reactApplicationContext) + } + + /** + * Gets any pending video conference action. + * Returns null if no pending action. + */ + @ReactMethod + override fun getPendingAction(promise: Promise) { + try { + val prefs = reactApplicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val action = prefs.getString(KEY_VIDEO_CONF_ACTION, null) + + // Clear the action after reading + action?.let { + prefs.edit().remove(KEY_VIDEO_CONF_ACTION).apply() + } + + promise.resolve(action) + } catch (e: Exception) { + promise.reject("ERROR", e.message) + } + } + + /** + * Clears any pending video conference action. + */ + @ReactMethod + override fun clearPendingAction() { + try { + reactApplicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + .edit() + .remove(KEY_VIDEO_CONF_ACTION) + .apply() + } catch (e: Exception) { + // Ignore errors + } + } +} diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt new file mode 100644 index 00000000000..b783e33e1b0 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt @@ -0,0 +1,210 @@ +package chat.rocket.reactnative.notification + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.media.AudioAttributes +import android.media.RingtoneManager +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.core.app.NotificationCompat +import chat.rocket.reactnative.MainActivity + +/** + * Handles video conference call notifications with call-style UI. + * Displays incoming call notifications with Accept/Decline actions. + */ +class VideoConfNotification(private val context: Context) { + + companion object { + private const val TAG = "RocketChat.VideoConf" + + const val CHANNEL_ID = "video-conf-call" + const val CHANNEL_NAME = "Video Calls" + + const val ACTION_ACCEPT = "chat.rocket.reactnative.ACTION_VIDEO_CONF_ACCEPT" + const val ACTION_DECLINE = "chat.rocket.reactnative.ACTION_VIDEO_CONF_DECLINE" + const val EXTRA_NOTIFICATION_DATA = "notification_data" + + /** + * Cancels a video call notification by notification ID. + */ + @JvmStatic + fun cancelById(context: Context, notificationId: Int) { + val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager + manager?.cancel(notificationId) + Log.d(TAG, "Video call notification cancelled with ID: $notificationId") + } + } + + private val notificationManager: NotificationManager? = + context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager + + init { + createNotificationChannel() + } + + /** + * Creates the notification channel for video calls with high importance and ringtone sound. + */ + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Incoming video conference calls" + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + + // Set ringtone sound + val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) + val audioAttributes = AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build() + setSound(ringtoneUri, audioAttributes) + } + + notificationManager?.createNotificationChannel(channel) + } + } + + /** + * Displays an incoming video call notification. + * + * @param bundle The notification data bundle + * @param ejson The parsed notification payload + */ + fun showIncomingCall(bundle: Bundle, ejson: Ejson) { + val rid = ejson.rid + // Video conf uses 'caller' field, regular messages use 'sender' + val callerId: String + val callerName: String + + if (ejson.caller != null) { + callerId = ejson.caller._id ?: "" + callerName = ejson.caller.name ?: "Unknown" + } else if (ejson.sender != null) { + // Fallback to sender if caller is not present + callerId = ejson.sender._id ?: "" + callerName = ejson.sender.name ?: ejson.senderName ?: "Unknown" + } else { + callerId = "" + callerName = "Unknown" + } + + // Generate unique notification ID from rid + callerId + val notificationIdStr = (rid + callerId).replace(Regex("[^A-Za-z0-9]"), "") + val notificationId = notificationIdStr.hashCode() + + Log.d(TAG, "Showing incoming call notification from: $callerName") + + // Create intent data for actions - include all required fields for JS + val intentData = Bundle().apply { + putString("rid", rid ?: "") + putString("notificationType", "videoconf") + putString("callerId", callerId) + putString("callerName", callerName) + putString("host", ejson.host ?: "") + putString("callId", ejson.callId ?: "") + putString("ejson", bundle.getString("ejson", "{}")) + putInt("notificationId", notificationId) + } + + Log.d(TAG, "Intent data - rid: $rid, callerId: $callerId, host: ${ejson.host}, callId: ${ejson.callId}") + + // Full screen intent - opens app when notification is tapped + val fullScreenIntent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + putExtras(intentData) + putExtra("event", "default") + } + + val fullScreenPendingIntent = createPendingIntent(notificationId, fullScreenIntent) + + // Accept action - directly opens MainActivity (Android 12+ blocks trampoline pattern) + val acceptIntent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + putExtras(intentData) + putExtra("event", "accept") + putExtra("videoConfAction", true) + action = "${ACTION_ACCEPT}_$notificationId" // Unique action to differentiate intents + } + + val acceptPendingIntent = createPendingIntent(notificationId + 1, acceptIntent) + + // Decline action - directly opens MainActivity + val declineIntent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + putExtras(intentData) + putExtra("event", "decline") + putExtra("videoConfAction", true) + action = "${ACTION_DECLINE}_$notificationId" // Unique action to differentiate intents + } + + val declinePendingIntent = createPendingIntent(notificationId + 2, declineIntent) + + // Get icons + val packageName = context.packageName + val smallIconResId = context.resources.getIdentifier("ic_notification", "drawable", packageName) + + // Build notification + val builder = NotificationCompat.Builder(context, CHANNEL_ID).apply { + setSmallIcon(smallIconResId) + setContentTitle("Incoming call") + setContentText("Video call from $callerName") + priority = NotificationCompat.PRIORITY_HIGH + setCategory(NotificationCompat.CATEGORY_CALL) + setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + setAutoCancel(false) + setOngoing(true) + setFullScreenIntent(fullScreenPendingIntent, true) + setContentIntent(fullScreenPendingIntent) + addAction(0, "Decline", declinePendingIntent) + addAction(0, "Accept", acceptPendingIntent) + } + + // Set sound for pre-O devices + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) + builder.setSound(ringtoneUri) + } + + // Show notification + notificationManager?.notify(notificationId, builder.build()) + Log.d(TAG, "Video call notification displayed with ID: $notificationId") + } + + /** + * Creates a PendingIntent with appropriate flags for the Android version. + */ + private fun createPendingIntent(requestCode: Int, intent: Intent): PendingIntent { + val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + return PendingIntent.getActivity(context, requestCode, intent, flags) + } + + /** + * Cancels a video call notification. + * + * @param rid The room ID + * @param callerId The caller's user ID + */ + fun cancelCall(rid: String, callerId: String) { + val notificationIdStr = (rid + callerId).replace(Regex("[^A-Za-z0-9]"), "") + val notificationId = notificationIdStr.hashCode() + + notificationManager?.cancel(notificationId) + Log.d(TAG, "Video call notification cancelled with ID: $notificationId") + } +} diff --git a/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfTurboPackage.kt b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfTurboPackage.kt new file mode 100644 index 00000000000..42e3f3eb211 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfTurboPackage.kt @@ -0,0 +1,37 @@ +package chat.rocket.reactnative.notification + +import com.facebook.react.TurboReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider + +/** + * React Native TurboModule package for video conference notification module. + */ +class VideoConfTurboPackage : TurboReactPackage() { + + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + return if (name == NativeVideoConfSpec.NAME) { + VideoConfModule(reactContext) + } else { + null + } + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + return ReactModuleInfoProvider { + mapOf( + NativeVideoConfSpec.NAME to ReactModuleInfo( + NativeVideoConfSpec.NAME, + NativeVideoConfSpec.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + false, // hasConstants + false, // isCxxModule + true // isTurboModule + ) + ) + } + } +} diff --git a/app/index.tsx b/app/index.tsx index 3e87e3bcaa0..88f8347445f 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -32,7 +32,7 @@ import { unsubscribeTheme } from './lib/methods/helpers/theme'; import { initializePushNotifications, onNotification } from './lib/notifications'; -import { getInitialNotification } from './lib/notifications/videoConf/getInitialNotification'; +import { getInitialNotification, setupVideoConfActionListener } from './lib/notifications/videoConf/getInitialNotification'; import store from './lib/store'; import { initStore } from './lib/store/auxStore'; import { type TSupportedThemes, ThemeContext } from './theme'; @@ -85,6 +85,7 @@ const parseDeepLinking = (url: string) => { export default class Root extends React.Component<{}, IState> { private listenerTimeout!: any; private dimensionsListener?: EmitterSubscription; + private videoConfActionCleanup?: () => void; constructor(props: any) { super(props); @@ -116,11 +117,15 @@ export default class Root extends React.Component<{}, IState> { }); }, 5000); this.dimensionsListener = Dimensions.addEventListener('change', this.onDimensionsChange); + + // Set up video conf action listener for background accept/decline + this.videoConfActionCleanup = setupVideoConfActionListener(); } componentWillUnmount() { clearTimeout(this.listenerTimeout); this.dimensionsListener?.remove?.(); + this.videoConfActionCleanup?.(); unsubscribeTheme(); } @@ -138,7 +143,10 @@ export default class Root extends React.Component<{}, IState> { return; } - await getInitialNotification(); + const handledVideoConf = await getInitialNotification(); + if (handledVideoConf) { + return; + } // Open app from deep linking const deepLinking = await Linking.getInitialURL(); diff --git a/app/lib/native/NativeVideoConfAndroid.ts b/app/lib/native/NativeVideoConfAndroid.ts new file mode 100644 index 00000000000..90346d97cb8 --- /dev/null +++ b/app/lib/native/NativeVideoConfAndroid.ts @@ -0,0 +1,9 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + getPendingAction(): Promise; + clearPendingAction(): void; +} + +export default TurboModuleRegistry.get('VideoConfModule'); diff --git a/app/lib/notifications/index.ts b/app/lib/notifications/index.ts index 1d7ad538494..65adab40d62 100644 --- a/app/lib/notifications/index.ts +++ b/app/lib/notifications/index.ts @@ -17,39 +17,47 @@ interface IEjson { export const onNotification = (push: INotification): void => { const identifier = String(push?.payload?.action?.identifier); + + // Handle video conf notification actions (Accept/Decline buttons) if (identifier === 'ACCEPT_ACTION' || identifier === 'DECLINE_ACTION') { - if (push?.payload && push?.payload?.ejson) { - const notification = EJSON.parse(push?.payload?.ejson); + if (push?.payload?.ejson) { + const notification = EJSON.parse(push.payload.ejson); store.dispatch(deepLinkingClickCallPush({ ...notification, event: identifier === 'ACCEPT_ACTION' ? 'accept' : 'decline' })); return; } } - if (push?.payload) { - try { - const notification = push?.payload; - if (notification.ejson) { - const { rid, name, sender, type, host, messageId }: IEjson = EJSON.parse(notification.ejson); - const types: Record = { - c: 'channel', - d: 'direct', - p: 'group', - l: 'channels' - }; - let roomName = type === SubscriptionType.DIRECT ? sender.username : name; - if (type === SubscriptionType.OMNICHANNEL) { - roomName = sender.name; - } + if (push?.payload?.ejson) { + try { + const notification = EJSON.parse(push.payload.ejson); - const params = { - host, - rid, - messageId, - path: `${types[type]}/${roomName}` - }; - store.dispatch(deepLinkingOpen(params)); + // Handle video conf notification tap (default action) - treat as accept + if (notification?.notificationType === 'videoconf') { + store.dispatch(deepLinkingClickCallPush({ ...notification, event: 'accept' })); return; } + + // Handle regular message notifications + const { rid, name, sender, type, host, messageId }: IEjson = notification; + const types: Record = { + c: 'channel', + d: 'direct', + p: 'group', + l: 'channels' + }; + let roomName = type === SubscriptionType.DIRECT ? sender.username : name; + if (type === SubscriptionType.OMNICHANNEL) { + roomName = sender.name; + } + + const params = { + host, + rid, + messageId, + path: `${types[type]}/${roomName}` + }; + store.dispatch(deepLinkingOpen(params)); + return; } catch (e) { console.warn(e); } @@ -58,12 +66,14 @@ export const onNotification = (push: INotification): void => { }; export const getDeviceToken = (): string => deviceToken; -export const setBadgeCount = (count?: number): void => setNotificationsBadgeCount(count); -export const removeNotificationsAndBadge = () => { - removeAllNotifications(); - setBadgeCount(); +export const setBadgeCount = (count?: number): void => { + setNotificationsBadgeCount(count); +}; +export const removeNotificationsAndBadge = async (): Promise => { + await removeAllNotifications(); + await setNotificationsBadgeCount(); }; -export const initializePushNotifications = (): Promise | undefined => { - setBadgeCount(); +export const initializePushNotifications = async (): Promise => { + await setNotificationsBadgeCount(); return pushNotificationConfigure(onNotification); }; diff --git a/app/lib/notifications/push.ts b/app/lib/notifications/push.ts index 639e83733ef..c5725ec6f4c 100644 --- a/app/lib/notifications/push.ts +++ b/app/lib/notifications/push.ts @@ -1,33 +1,163 @@ -import { - Notifications, - type Registered, - type RegistrationError, - type NotificationCompletion, - type Notification, - NotificationAction, - NotificationCategory -} from 'react-native-notifications'; -import { PermissionsAndroid, Platform } from 'react-native'; +import * as Notifications from 'expo-notifications'; +import * as Device from 'expo-device'; +import { Platform } from 'react-native'; import { type INotification } from '../../definitions'; import { isIOS } from '../methods/helpers'; import { store as reduxStore } from '../store/auxStore'; +import { registerPushToken } from '../services/restApi'; import I18n from '../../i18n'; export let deviceToken = ''; -export const setNotificationsBadgeCount = (count = 0): void => { - if (isIOS) { - Notifications.ios.setBadgeCount(count); +export const setNotificationsBadgeCount = async (count = 0): Promise => { + try { + await Notifications.setBadgeCountAsync(count); + } catch (e) { + console.log('Failed to set badge count:', e); } }; -export const removeAllNotifications = (): void => { - Notifications.removeAllDeliveredNotifications(); +export const removeAllNotifications = async (): Promise => { + try { + await Notifications.dismissAllNotificationsAsync(); + } catch (e) { + console.log('Failed to dismiss notifications:', e); + } }; let configured = false; +/** + * Transform expo-notifications response to the INotification format expected by the app + */ +const transformNotificationResponse = (response: Notifications.NotificationResponse): INotification => { + const { notification, actionIdentifier, userText } = response; + const { trigger, content } = notification.request; + + // Get the raw data from the notification + let payload: Record = {}; + + if (trigger && 'type' in trigger && trigger.type === 'push') { + if (Platform.OS === 'android' && 'remoteMessage' in trigger && trigger.remoteMessage) { + // Android: data comes from remoteMessage.data + payload = trigger.remoteMessage.data || {}; + } else if (Platform.OS === 'ios' && 'payload' in trigger && trigger.payload) { + // iOS: data comes from payload (userInfo) + payload = trigger.payload as Record; + } + } + + // Fallback to content.data if trigger data is not available + if (Object.keys(payload).length === 0 && content.data) { + payload = content.data as Record; + } + + // Add action identifier if it's a specific action (not default tap) + if (actionIdentifier && actionIdentifier !== Notifications.DEFAULT_ACTION_IDENTIFIER) { + payload.action = { identifier: actionIdentifier }; + if (userText) { + payload.action.userText = userText; + } + } + + return { + payload: { + message: content.body || payload.message || '', + style: payload.style || '', + ejson: payload.ejson || '', + collapse_key: payload.collapse_key || '', + notId: payload.notId || notification.request.identifier || '', + msgcnt: payload.msgcnt || '', + title: content.title || payload.title || '', + from: payload.from || '', + image: payload.image || '', + soundname: payload.soundname || '', + action: payload.action + }, + identifier: notification.request.identifier + }; +}; + +/** + * Set up notification categories for iOS (actions like Reply, Accept, Decline) + */ +const setupNotificationCategories = async (): Promise => { + if (!isIOS) { + return; + } + + try { + // Message category with Reply action + await Notifications.setNotificationCategoryAsync('MESSAGE', [ + { + identifier: 'REPLY_ACTION', + buttonTitle: I18n.t('Reply'), + textInput: { + submitButtonTitle: I18n.t('Reply'), + placeholder: I18n.t('Type_message') + }, + options: { + opensAppToForeground: false + } + } + ]); + + // Video conference category with Accept/Decline actions + await Notifications.setNotificationCategoryAsync('VIDEOCONF', [ + { + identifier: 'ACCEPT_ACTION', + buttonTitle: I18n.t('accept'), + options: { + opensAppToForeground: true + } + }, + { + identifier: 'DECLINE_ACTION', + buttonTitle: I18n.t('decline'), + options: { + opensAppToForeground: true + } + } + ]); + } catch (e) { + console.log('Failed to set notification categories:', e); + } +}; + +/** + * Request notification permissions and register for push notifications + */ +const registerForPushNotifications = async (): Promise => { + if (!Device.isDevice) { + console.log('Push notifications require a physical device'); + return null; + } + + try { + // Check and request permissions + const { status: existingStatus } = await Notifications.getPermissionsAsync(); + let finalStatus = existingStatus; + + if (existingStatus !== 'granted') { + const { status } = await Notifications.requestPermissionsAsync(); + finalStatus = status; + } + + if (finalStatus !== 'granted') { + console.log('Failed to get push notification permissions'); + return null; + } + + // Get the device push token (FCM for Android, APNs for iOS) + const tokenData = await Notifications.getDevicePushTokenAsync(); + return tokenData.data; + } catch (e) { + console.log('Error registering for push notifications:', e); + return null; + } +}; + export const pushNotificationConfigure = (onNotification: (notification: INotification) => void): Promise => { if (configured) { return Promise.resolve({ configured: true }); @@ -35,50 +165,44 @@ export const pushNotificationConfigure = (onNotification: (notification: INotifi configured = true; - if (isIOS) { - // init - Notifications.ios.registerRemoteNotifications(); - - const notificationAction = new NotificationAction('REPLY_ACTION', 'background', I18n.t('Reply'), true, { - buttonTitle: I18n.t('Reply'), - placeholder: I18n.t('Type_message') - }); - const acceptAction = new NotificationAction('ACCEPT_ACTION', 'foreground', I18n.t('accept'), true); - const rejectAction = new NotificationAction('DECLINE_ACTION', 'foreground', I18n.t('decline'), true); - - const notificationCategory = new NotificationCategory('MESSAGE', [notificationAction]); - const videoConfCategory = new NotificationCategory('VIDEOCONF', [acceptAction, rejectAction]); - - Notifications.setCategories([videoConfCategory, notificationCategory]); - } else if (Platform.OS === 'android' && Platform.constants.Version >= 33) { - // @ts-ignore - PermissionsAndroid.request('android.permission.POST_NOTIFICATIONS').then(permissionStatus => { - if (permissionStatus === 'granted') { - Notifications.registerRemoteNotifications(); - } else { - // TODO: Ask user to enable notifications - } - }); - } else { - Notifications.registerRemoteNotifications(); - } - - Notifications.events().registerRemoteNotificationsRegistered((event: Registered) => { - deviceToken = event.deviceToken; + // Set up how notifications should be handled when the app is in foreground + Notifications.setNotificationHandler({ + handleNotification: () => + Promise.resolve({ + shouldShowAlert: false, + shouldPlaySound: false, + shouldSetBadge: false, + shouldShowBanner: false, + shouldShowList: false + }) }); - Notifications.events().registerRemoteNotificationsRegistrationFailed((event: RegistrationError) => { - // TODO: Handle error - console.log(event); + // Set up notification categories for iOS + setupNotificationCategories(); + + // Register for push notifications and get token + registerForPushNotifications().then(token => { + if (token) { + deviceToken = token; + } }); - Notifications.events().registerNotificationReceivedForeground( - (notification: Notification, completion: (response: NotificationCompletion) => void) => { - completion({ alert: false, sound: false, badge: false }); + // Listen for token updates (FCM can refresh tokens at any time) + Notifications.addPushTokenListener(tokenData => { + deviceToken = tokenData.data; + // Re-register with server if user is logged in + const { isAuthenticated } = reduxStore.getState().login; + if (isAuthenticated) { + registerPushToken().catch(e => { + console.log('Failed to re-register push token after refresh:', e); + }); } - ); + }); + + // Listen for notification responses (when user taps on notification) + Notifications.addNotificationResponseReceivedListener(response => { + const notification = transformNotificationResponse(response); - Notifications.events().registerNotificationOpened((notification: Notification, completion: () => void) => { if (isIOS) { const { background } = reduxStore.getState().app; if (background) { @@ -87,14 +211,13 @@ export const pushNotificationConfigure = (onNotification: (notification: INotifi } else { onNotification(notification); } - completion(); }); - Notifications.events().registerNotificationReceivedBackground( - (notification: Notification, completion: (response: any) => void) => { - completion({ alert: true, sound: true, badge: false }); - } - ); + // Get initial notification (app was opened by tapping a notification) + const lastResponse = Notifications.getLastNotificationResponse(); + if (lastResponse) { + return Promise.resolve(transformNotificationResponse(lastResponse)); + } - return Notifications.getInitialNotification(); + return Promise.resolve(null); }; diff --git a/app/lib/notifications/videoConf/backgroundNotificationHandler.ts b/app/lib/notifications/videoConf/backgroundNotificationHandler.ts deleted file mode 100644 index ca33cc13787..00000000000 --- a/app/lib/notifications/videoConf/backgroundNotificationHandler.ts +++ /dev/null @@ -1,125 +0,0 @@ -import notifee, { AndroidCategory, AndroidFlags, AndroidImportance, AndroidVisibility, type Event } from '@notifee/react-native'; -import { getMessaging as messaging } from '@react-native-firebase/messaging'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import ejson from 'ejson'; - -import { deepLinkingClickCallPush } from '../../../actions/deepLinking'; -import i18n from '../../../i18n'; -import { colors } from '../../constants/colors'; -import { store } from '../../store/auxStore'; - -const VIDEO_CONF_CHANNEL = 'video-conf-call'; -const VIDEO_CONF_TYPE = 'videoconf'; - -interface Caller { - _id?: string; - name?: string; -} - -interface NotificationData { - notificationType?: string; - status?: number; - rid?: string; - caller?: Caller; -} - -const createChannel = () => - notifee.createChannel({ - id: VIDEO_CONF_CHANNEL, - name: 'Video Call', - lights: true, - vibration: true, - importance: AndroidImportance.HIGH, - sound: 'ringtone' - }); - -const handleBackgroundEvent = async (event: Event) => { - const { pressAction, notification } = event.detail; - const notificationData = notification?.data; - if ( - typeof notificationData?.caller === 'object' && - (notificationData.caller as Caller)?._id && - (event.type === 1 || event.type === 2) - ) { - if (store?.getState()?.app.ready) { - store.dispatch(deepLinkingClickCallPush({ ...notificationData, event: pressAction?.id })); - } else { - AsyncStorage.setItem('pushNotification', JSON.stringify({ ...notificationData, event: pressAction?.id })); - } - await notifee.cancelNotification( - `${notificationData.rid}${(notificationData.caller as Caller)._id}`.replace(/[^A-Za-z0-9]/g, '') - ); - } -}; - -const backgroundNotificationHandler = () => { - notifee.onBackgroundEvent(handleBackgroundEvent); -}; - -const displayVideoConferenceNotification = async (notification: NotificationData) => { - const id = `${notification.rid}${notification.caller?._id}`.replace(/[^A-Za-z0-9]/g, ''); - const actions = [ - { - title: i18n.t('accept'), - pressAction: { - id: 'accept', - launchActivity: 'default' - } - }, - { - title: i18n.t('decline'), - pressAction: { - id: 'decline', - launchActivity: 'default' - } - } - ]; - - await notifee.displayNotification({ - id, - title: i18n.t('conference_call'), - body: `${i18n.t('Incoming_call_from')} ${notification.caller?.name}`, - data: notification as { [key: string]: string | number | object }, - android: { - channelId: VIDEO_CONF_CHANNEL, - category: AndroidCategory.CALL, - visibility: AndroidVisibility.PUBLIC, - importance: AndroidImportance.HIGH, - smallIcon: 'ic_notification', - color: colors.light.badgeBackgroundLevel4, - actions, - lightUpScreen: true, - loopSound: true, - sound: 'ringtone', - autoCancel: false, - ongoing: true, - pressAction: { - id: 'default', - launchActivity: 'default' - }, - flags: [AndroidFlags.FLAG_NO_CLEAR] - } - }); -}; - -const setBackgroundNotificationHandler = () => { - createChannel(); - messaging().setBackgroundMessageHandler(async message => { - if (message?.data?.ejson) { - const notification: NotificationData = ejson.parse(message?.data?.ejson as string); - if (notification?.notificationType === VIDEO_CONF_TYPE) { - if (notification.status === 0) { - await displayVideoConferenceNotification(notification); - } else if (notification.status === 4) { - const id = `${notification.rid}${notification.caller?._id}`.replace(/[^A-Za-z0-9]/g, ''); - await notifee.cancelNotification(id); - } - } - } - - return null; - }); -}; - -setBackgroundNotificationHandler(); -backgroundNotificationHandler(); diff --git a/app/lib/notifications/videoConf/getInitialNotification.ts b/app/lib/notifications/videoConf/getInitialNotification.ts index 5e48a842ce1..b70b45b0adc 100644 --- a/app/lib/notifications/videoConf/getInitialNotification.ts +++ b/app/lib/notifications/videoConf/getInitialNotification.ts @@ -1,15 +1,85 @@ +import * as Notifications from 'expo-notifications'; +import EJSON from 'ejson'; +import { DeviceEventEmitter, Platform } from 'react-native'; + import { deepLinkingClickCallPush } from '../../../actions/deepLinking'; -import { isAndroid } from '../../methods/helpers'; import { store } from '../../store/auxStore'; +import NativeVideoConfModule from '../../native/NativeVideoConfAndroid'; + +/** + * Sets up listener for video conference actions from native side. + * This handles the case when app is in background and user taps Accept/Decline. + */ +export const setupVideoConfActionListener = (): (() => void) | undefined => { + if (Platform.OS === 'android') { + const subscription = DeviceEventEmitter.addListener('VideoConfAction', (actionJson: string) => { + try { + const data = JSON.parse(actionJson); + if (data?.notificationType === 'videoconf') { + store.dispatch(deepLinkingClickCallPush(data)); + } + } catch (error) { + console.log('Error handling video conf action event:', error); + } + }); + + // Return cleanup function + return () => subscription.remove(); + } + return undefined; +}; -export const getInitialNotification = async (): Promise => { - if (isAndroid) { - const notifee = require('@notifee/react-native').default; - const initialNotification = await notifee.getInitialNotification(); - if (initialNotification?.notification?.data?.notificationType === 'videoconf') { - store.dispatch( - deepLinkingClickCallPush({ ...initialNotification?.notification?.data, event: initialNotification?.pressAction?.id }) - ); +/** + * Check for pending video conference actions from native notification handling. + * @returns true if a video conf action was found and dispatched, false otherwise + */ +export const getInitialNotification = async (): Promise => { + // Android: Check native module for pending action + if (Platform.OS === 'android' && NativeVideoConfModule) { + try { + const pendingAction = await NativeVideoConfModule.getPendingAction(); + if (pendingAction) { + const data = JSON.parse(pendingAction); + if (data?.notificationType === 'videoconf') { + store.dispatch(deepLinkingClickCallPush(data)); + return true; + } + } + } catch (error) { + console.log('Error getting video conf initial notification:', error); } } + + // iOS: Check expo-notifications for last response with video conf action + if (Platform.OS === 'ios') { + try { + const lastResponse = await Notifications.getLastNotificationResponseAsync(); + if (lastResponse) { + const { actionIdentifier, notification } = lastResponse; + const { trigger } = notification.request; + let payload: Record = {}; + + if (trigger && 'type' in trigger && trigger.type === 'push' && 'payload' in trigger && trigger.payload) { + payload = trigger.payload as Record; + } + + if (payload.ejson) { + const ejsonData = EJSON.parse(payload.ejson); + if (ejsonData?.notificationType === 'videoconf') { + // Accept/Decline actions or default tap (treat as accept) + let event = 'accept'; + if (actionIdentifier === 'DECLINE_ACTION') { + event = 'decline'; + } + store.dispatch(deepLinkingClickCallPush({ ...ejsonData, event })); + return true; + } + } + } + } catch (error) { + console.log('Error getting iOS video conf initial notification:', error); + } + } + + return false; }; diff --git a/app/lib/store/appStateMiddleware.ts b/app/lib/store/appStateMiddleware.ts index 61da57a6c94..c83d1c9d5d4 100644 --- a/app/lib/store/appStateMiddleware.ts +++ b/app/lib/store/appStateMiddleware.ts @@ -17,7 +17,7 @@ export default () => let type; if (nextAppState === 'active') { type = APP_STATE.FOREGROUND; - removeNotificationsAndBadge(); + removeNotificationsAndBadge().catch(e => console.warn('Failed to clear notifications', e)); } else if (nextAppState === 'background') { type = APP_STATE.BACKGROUND; } diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index abc28aba567..7afcadc7b92 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -223,7 +223,7 @@ const handleNavigateCallRoom = function* handleNavigateCallRoom({ params }) { if (room) { const isMasterDetail = yield select(state => state.app.isMasterDetail); yield navigateToRoom({ item: room, isMasterDetail }); - const uid = params.caller._id; + const uid = params.caller?._id; const { rid, callId, event } = params; if (event === 'accept') { yield call(notifyUser, `${uid}/video-conference`, { @@ -246,6 +246,10 @@ const handleNavigateCallRoom = function* handleNavigateCallRoom({ params }) { const handleClickCallPush = function* handleClickCallPush({ params }) { let { host } = params; + if (!host) { + return; + } + if (host.slice(-1) === '/') { host = host.slice(0, host.length - 1); } diff --git a/app/sagas/troubleshootingNotification.ts b/app/sagas/troubleshootingNotification.ts index 0717c083f19..193568890e9 100644 --- a/app/sagas/troubleshootingNotification.ts +++ b/app/sagas/troubleshootingNotification.ts @@ -1,6 +1,6 @@ import { type Action } from 'redux'; import { call, takeLatest, put } from 'typed-redux-saga'; -import notifee, { AuthorizationStatus } from '@notifee/react-native'; +import * as Notifications from 'expo-notifications'; import { TROUBLESHOOTING_NOTIFICATION } from '../actions/actionsTypes'; import { setTroubleshootingNotification } from '../actions/troubleshootingNotification'; @@ -19,8 +19,8 @@ function* init() { let defaultPushGateway = false; let pushGatewayEnabled = false; try { - const { authorizationStatus } = yield* call(notifee.getNotificationSettings); - deviceNotificationEnabled = authorizationStatus > AuthorizationStatus.DENIED; + const { status } = yield* call(Notifications.getPermissionsAsync); + deviceNotificationEnabled = status === 'granted'; } catch (e) { log(e); } diff --git a/app/views/JitsiMeetView/index.tsx b/app/views/JitsiMeetView/index.tsx index 67544b8de63..fe86c05d1c7 100644 --- a/app/views/JitsiMeetView/index.tsx +++ b/app/views/JitsiMeetView/index.tsx @@ -2,7 +2,7 @@ import CookieManager from '@react-native-cookies/cookies'; import { type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake'; import React, { useCallback, useEffect, useState } from 'react'; -import { ActivityIndicator, BackHandler, Linking, SafeAreaView, StyleSheet, View } from 'react-native'; +import { ActivityIndicator, Linking, SafeAreaView, StyleSheet, View } from 'react-native'; import WebView, { type WebViewNavigation } from 'react-native-webview'; import { userAgent } from '../../lib/constants/userAgent'; @@ -52,8 +52,8 @@ const JitsiMeetView = (): React.ReactElement => { await Linking.openURL(`org.jitsi.meet://${callUrl}`); goBack(); } catch (error) { - // As the jitsi app was not opened, disable the backhandler on android - BackHandler.addEventListener('hardwareBackPress', () => true); + // Jitsi app not installed - will use WebView instead + // No need to block back button as WebView handles navigation } }, [goBack, url]); diff --git a/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx b/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx index 9e77e43c47b..0adf5702b9d 100644 --- a/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx +++ b/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx @@ -1,4 +1,3 @@ -import notifee from '@notifee/react-native'; import React from 'react'; import { Linking } from 'react-native'; @@ -19,7 +18,7 @@ export default function DeviceNotificationSettings(): React.ReactElement { if (isIOS) { Linking.openURL('app-settings:'); } else { - notifee.openNotificationSettings(); + Linking.openSettings(); } }; diff --git a/index.js b/index.js index 4844e1da5dd..778e46478d1 100644 --- a/index.js +++ b/index.js @@ -22,9 +22,8 @@ if (process.env.USE_STORYBOOK) { LogBox.ignoreAllLogs(); - if (isAndroid) { - require('./app/lib/notifications/videoConf/backgroundNotificationHandler'); - } + // Note: Android video conference notifications are now handled natively + // in RCFirebaseMessagingService -> CustomPushNotification -> VideoConfNotification AppRegistry.registerComponent(appName, () => require('./app/index').default); } diff --git a/ios/AppDelegate.swift b/ios/AppDelegate.swift index 4303a0d39bb..b0aa0e2851f 100644 --- a/ios/AppDelegate.swift +++ b/ios/AppDelegate.swift @@ -24,9 +24,6 @@ public class AppDelegate: ExpoAppDelegate { FirebaseApp.configure() Bugsnag.start() - - // Initialize notifications - RNNotifications.startMonitorNotifications() ReplyNotification.configure() let delegate = ReactNativeDelegate() @@ -61,19 +58,6 @@ public class AppDelegate: ExpoAppDelegate { return result } - // Remote Notification handling - public override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - RNNotifications.didRegisterForRemoteNotifications(withDeviceToken: deviceToken) - } - - public override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - RNNotifications.didFailToRegisterForRemoteNotificationsWithError(error) - } - - public override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - RNNotifications.didReceiveBackgroundNotification(userInfo, withCompletionHandler: completionHandler) - } - // Linking API public override func application( _ app: UIApplication, diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index 1d18a413e39..d337a0061df 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -13,11 +13,18 @@ class NotificationService: UNNotificationServiceExtension { if let bestAttemptContent = bestAttemptContent { let ejson = (bestAttemptContent.userInfo["ejson"] as? String ?? "").data(using: .utf8)! guard let data = try? (JSONDecoder().decode(Payload.self, from: ejson)) else { + contentHandler(bestAttemptContent) return } rocketchat = RocketChat(server: data.host.removeTrailingSlash()) + // Handle video conference notifications + if data.notificationType == .videoconf { + self.processVideoConf(payload: data, request: request) + return + } + // If the notification has the content on the payload, show it if data.notificationType != .messageIdOnly { self.processPayload(payload: data) @@ -35,17 +42,66 @@ class NotificationService: UNNotificationServiceExtension { } // Request the content from server - self.rocketchat?.getPushWithId(data.messageId) { notification in - if let notification = notification { - self.bestAttemptContent?.title = notification.title - self.bestAttemptContent?.body = notification.text - self.processPayload(payload: notification.payload) + if let messageId = data.messageId { + self.rocketchat?.getPushWithId(messageId) { notification in + if let notification = notification { + self.bestAttemptContent?.title = notification.title + self.bestAttemptContent?.body = notification.text + + // Update ejson with full payload from server for correct navigation + if let payloadData = try? JSONEncoder().encode(notification.payload), + let payloadString = String(data: payloadData, encoding: .utf8) { + self.bestAttemptContent?.userInfo["ejson"] = payloadString + } + + self.processPayload(payload: notification.payload) + } else { + // Server returned no notification, deliver as-is + if let bestAttemptContent = self.bestAttemptContent { + self.contentHandler?(bestAttemptContent) + } + } + } + } else { + // No messageId available, deliver the notification as-is + if let bestAttemptContent = self.bestAttemptContent { + self.contentHandler?(bestAttemptContent) } } } } } + func processVideoConf(payload: Payload, request: UNNotificationRequest) { + guard let bestAttemptContent = bestAttemptContent else { + return + } + + // Status 4 means call cancelled/ended - remove any existing notification + if payload.status == 4 { + if let rid = payload.rid, let callerId = payload.caller?._id { + let notificationId = "\(rid)\(callerId)".replacingOccurrences(of: "[^A-Za-z0-9]", with: "", options: .regularExpression) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notificationId]) + } + // Don't show anything for cancelled calls + contentHandler?(UNNotificationContent()) + return + } + + // Status 0 (or nil) means incoming call - show notification with actions + let callerName = payload.caller?.name ?? "Unknown" + + bestAttemptContent.title = NSLocalizedString("Video Call", comment: "") + bestAttemptContent.body = String(format: NSLocalizedString("Incoming call from %@", comment: ""), callerName) + bestAttemptContent.categoryIdentifier = "VIDEOCONF" + bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName("ringtone.mp3")) + if #available(iOS 15.0, *) { + bestAttemptContent.interruptionLevel = .timeSensitive + } + + contentHandler?(bestAttemptContent) + } + func processPayload(payload: Payload) { // If is a encrypted message if payload.messageType == .e2e { diff --git a/ios/Podfile b/ios/Podfile index 128259fad07..4d852a82cd3 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -36,8 +36,7 @@ end $static_framework = [ 'WatermelonDB', - 'simdjson', - 'react-native-notifications' + 'simdjson' ] pre_install do |installer| Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 60d4d99c035..34cc3e5cd90 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -27,11 +27,15 @@ PODS: - BVLinearGradient (2.6.2): - React-Core - DoubleConversion (1.1.6) + - EXApplication (7.0.8): + - ExpoModulesCore - EXAV (15.1.3): - ExpoModulesCore - ReactCommon/turbomodule/core - EXConstants (17.1.5): - ExpoModulesCore + - EXNotifications (0.32.11): + - ExpoModulesCore - Expo (53.0.7): - DoubleConversion - ExpoModulesCore @@ -67,6 +71,8 @@ PODS: - ExpoModulesCore - ZXingObjC/OneD - ZXingObjC/PDF417 + - ExpoDevice (8.0.10): + - ExpoModulesCore - ExpoDocumentPicker (13.1.4): - ExpoModulesCore - ExpoFileSystem (18.1.7): @@ -1709,8 +1715,6 @@ PODS: - Yoga - react-native-netinfo (11.3.1): - React-Core - - react-native-notifications (5.1.0): - - React-Core - react-native-restart (0.0.22): - React-Core - react-native-safe-area-context (5.4.0): @@ -2378,11 +2382,6 @@ PODS: - Yoga - RNLocalize (2.1.1): - React-Core - - RNNotifee (7.8.2): - - React-Core - - RNNotifee/NotifeeCore (= 7.8.2) - - RNNotifee/NotifeeCore (7.8.2): - - React-Core - RNReanimated (3.17.1): - DoubleConversion - glog @@ -2636,12 +2635,15 @@ DEPENDENCIES: - "BugsnagReactNative (from `../node_modules/@bugsnag/react-native`)" - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EXApplication (from `../node_modules/expo-application/ios`) - EXAV (from `../node_modules/expo-av/ios`) - EXConstants (from `../node_modules/expo-constants/ios`) + - EXNotifications (from `../node_modules/expo-notifications/ios`) - Expo (from `../node_modules/expo`) - ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`) - ExpoAsset (from `../node_modules/expo-asset/ios`) - ExpoCamera (from `../node_modules/expo-camera/ios`) + - ExpoDevice (from `../node_modules/expo-device/ios`) - ExpoDocumentPicker (from `../node_modules/expo-document-picker/ios`) - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - ExpoFont (from `../node_modules/expo-font/ios`) @@ -2699,7 +2701,6 @@ DEPENDENCIES: - react-native-keyboard-controller (from `../node_modules/react-native-keyboard-controller`) - react-native-mmkv (from `../node_modules/react-native-mmkv`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - - react-native-notifications (from `../node_modules/react-native-notifications`) - react-native-restart (from `../node_modules/react-native-restart`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - "react-native-slider (from `../node_modules/@react-native-community/slider`)" @@ -2750,7 +2751,6 @@ DEPENDENCIES: - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`) - RNLocalize (from `../node_modules/react-native-localize`) - - "RNNotifee (from `../node_modules/@notifee/react-native`)" - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) - RNSVG (from `../node_modules/react-native-svg`) @@ -2795,10 +2795,14 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-linear-gradient" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + EXApplication: + :path: "../node_modules/expo-application/ios" EXAV: :path: "../node_modules/expo-av/ios" EXConstants: :path: "../node_modules/expo-constants/ios" + EXNotifications: + :path: "../node_modules/expo-notifications/ios" Expo: :path: "../node_modules/expo" ExpoAppleAuthentication: @@ -2807,6 +2811,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-asset/ios" ExpoCamera: :path: "../node_modules/expo-camera/ios" + ExpoDevice: + :path: "../node_modules/expo-device/ios" ExpoDocumentPicker: :path: "../node_modules/expo-document-picker/ios" ExpoFileSystem: @@ -2918,8 +2924,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-mmkv" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" - react-native-notifications: - :path: "../node_modules/react-native-notifications" react-native-restart: :path: "../node_modules/react-native-restart" react-native-safe-area-context: @@ -3020,8 +3024,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-image-crop-picker" RNLocalize: :path: "../node_modules/react-native-localize" - RNNotifee: - :path: "../node_modules/@notifee/react-native" RNReanimated: :path: "../node_modules/react-native-reanimated" RNScreens: @@ -3040,12 +3042,15 @@ SPEC CHECKSUMS: BugsnagReactNative: 8150cc1facb5c69c7a5d27d614fc50b4ed03c2b8 BVLinearGradient: 7815a70ab485b7b155186dd0cc836363e0288cad DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb + EXApplication: 1e98d4b1dccdf30627f92917f4b2c5a53c330e5f EXAV: 90c33266835bf7e61dc0731fa8d82833d22d286f EXConstants: a1112af878fddfe6acc0399473ac56a07ced0f47 + EXNotifications: 7a2975f4e282b827a0bc78bb1d232650cb569bbd Expo: bb70dfd014457bcca19f33e8c783afdc18308434 ExpoAppleAuthentication: b589b71be6bb817decf8f35e92c6281365140289 ExpoAsset: 3bc9adb7dbbf27ae82c18ca97eb988a3ae7e73b1 ExpoCamera: 105a9a963c443a3e112c51dd81290d81cd8da94a + ExpoDevice: 6327c3c200816795708885adf540d26ecab83d1a ExpoDocumentPicker: 344f16224e6a8a088f2693667a8b713160f8f57b ExpoFileSystem: 175267faf2b38511b01ac110243b13754dac57d3 ExpoFont: abbb91a911eb961652c2b0a22eef801860425ed6 @@ -3119,7 +3124,6 @@ SPEC CHECKSUMS: react-native-keyboard-controller: 9ec7ee23328c30251a399cffd8b54324a00343bf react-native-mmkv: ec96a16cd90e0d994d486c3993abf712186f7262 react-native-netinfo: 2e3c27627db7d49ba412bfab25834e679db41e21 - react-native-notifications: 3bafa1237ae8a47569a84801f17d80242fe9f6a5 react-native-restart: f6f591aeb40194c41b9b5013901f00e6cf7d0f29 react-native-safe-area-context: 5928d84c879db2f9eb6969ca70e68f58623dbf25 react-native-slider: 605e731593322c4bb2eb48d7d64e2e4dbf7cbd77 @@ -3153,7 +3157,7 @@ SPEC CHECKSUMS: React-timing: 2d07431f1c1203c5b0aaa6dc7b5f503704519218 React-utils: 67cf7dcfc18aa4c56bec19e11886033bb057d9fa ReactAppDependencyProvider: bf62814e0fde923f73fc64b7e82d76c63c284da9 - ReactCodegen: df3ff45729335a27d1c85bed1787e79783289968 + ReactCodegen: 8885059f55a205c667f52d6e35877137631116f2 ReactCommon: 177fca841e97b2c0e288e86097b8be04c6e7ae36 RNBootSplash: 1280eeb18d887de0a45bb4923d4fc56f25c8b99c RNCAsyncStorage: edb872909c88d8541c0bfade3f86cd7784a7c6b3 @@ -3170,7 +3174,6 @@ SPEC CHECKSUMS: RNGestureHandler: 8ff2b1434b0ff8bab28c8242a656fb842990bbc8 RNImageCropPicker: b219389d3a300679b396e81d501e8c8169ffa3c0 RNLocalize: ca86348d88b9a89da0e700af58d428ab3f343c4e - RNNotifee: 8768d065bf1e2f9f8f347b4bd79147431c7eacd6 RNReanimated: f52ccd5ceea2bae48d7421eec89b3f0c10d7b642 RNScreens: b13e4c45f0406f33986a39c0d8da0324bff94435 RNSVG: 680e961f640e381aab730a04b2371969686ed9f7 @@ -3185,6 +3188,6 @@ SPEC CHECKSUMS: Yoga: dfabf1234ccd5ac41d1b1d43179f024366ae9831 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: 757f1420bea093d5dadd6c82f2653ddd1ad8eb9b +PODFILE CHECKSUM: 199f6fbbe6fb415c822cca992e6152000ac55b3e COCOAPODS: 1.15.2 diff --git a/ios/ReplyNotification.swift b/ios/ReplyNotification.swift index cffb486528e..4c3ea7c2183 100644 --- a/ios/ReplyNotification.swift +++ b/ios/ReplyNotification.swift @@ -9,49 +9,110 @@ import Foundation import UserNotifications +// Handles direct reply from iOS notifications. +// Intercepts REPLY_ACTION responses and sends messages natively, +// while forwarding all other notification events to expo-notifications. @objc(ReplyNotification) -class ReplyNotification: RNNotificationEventHandler { - private static let dispatchOnce: Void = { - let instance: AnyClass! = object_getClass(ReplyNotification()) - let originalMethod = class_getInstanceMethod(instance, #selector(didReceive)) - let swizzledMethod = class_getInstanceMethod(instance, #selector(replyNotification_didReceiveNotificationResponse)) - if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod { - method_exchangeImplementations(originalMethod, swizzledMethod) - } - }() +class ReplyNotification: NSObject, UNUserNotificationCenterDelegate { + private static var shared: ReplyNotification? + private weak var originalDelegate: UNUserNotificationCenterDelegate? @objc public static func configure() { - _ = self.dispatchOnce + let instance = ReplyNotification() + shared = instance + + // Store the original delegate (expo-notifications) and set ourselves as the delegate + let center = UNUserNotificationCenter.current() + instance.originalDelegate = center.delegate + center.delegate = instance } - @objc - func replyNotification_didReceiveNotificationResponse(_ response: UNNotificationResponse, completionHandler: @escaping(() -> Void)) { + // MARK: - UNUserNotificationCenterDelegate + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + // Handle REPLY_ACTION natively if response.actionIdentifier == "REPLY_ACTION" { - if let notification = RCTConvert.unNotificationPayload(response.notification) { - if let data = (notification["ejson"] as? String)?.data(using: .utf8) { - if let payload = try? JSONDecoder().decode(Payload.self, from: data), let rid = payload.rid { - if let msg = (response as? UNTextInputNotificationResponse)?.userText { - let rocketchat = RocketChat(server: payload.host.removeTrailingSlash()) - let backgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) - - rocketchat.sendMessage(rid: rid, message: msg, threadIdentifier: payload.tmid) { response in - guard let response = response, response.success else { - let content = UNMutableNotificationContent() - content.body = "Failed to reply message." - let request = UNNotificationRequest(identifier: "replyFailure", content: content, trigger: nil) - UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) - return - } - UIApplication.shared.endBackgroundTask(backgroundTask) - } - } - } + handleReplyAction(response: response, completionHandler: completionHandler) + return + } + + // Forward to original delegate (expo-notifications) + if let originalDelegate = originalDelegate { + originalDelegate.userNotificationCenter?(center, didReceive: response, withCompletionHandler: completionHandler) + } else { + completionHandler() + } + } + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + // Forward to original delegate (expo-notifications) + if let originalDelegate = originalDelegate { + originalDelegate.userNotificationCenter?(center, willPresent: notification, withCompletionHandler: completionHandler) + } else { + completionHandler([]) + } + } + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + openSettingsFor notification: UNNotification? + ) { + // Forward to original delegate (expo-notifications) + if let originalDelegate = originalDelegate { + if #available(iOS 12.0, *) { + originalDelegate.userNotificationCenter?(center, openSettingsFor: notification) + } + } + } + + // MARK: - Reply Handling + + private func handleReplyAction(response: UNNotificationResponse, completionHandler: @escaping () -> Void) { + guard let textResponse = response as? UNTextInputNotificationResponse else { + completionHandler() + return + } + + let userInfo = response.notification.request.content.userInfo + + guard let ejsonString = userInfo["ejson"] as? String, + let ejsonData = ejsonString.data(using: .utf8), + let payload = try? JSONDecoder().decode(Payload.self, from: ejsonData), + let rid = payload.rid else { + completionHandler() + return + } + + let message = textResponse.userText + let rocketchat = RocketChat(server: payload.host.removeTrailingSlash()) + let backgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) + + rocketchat.sendMessage(rid: rid, message: message, threadIdentifier: payload.tmid) { response in + // Ensure we're on the main thread for UI operations + DispatchQueue.main.async { + defer { + UIApplication.shared.endBackgroundTask(backgroundTask) + completionHandler() + } + + guard let response = response, response.success else { + // Show failure notification + let content = UNMutableNotificationContent() + content.body = "Failed to reply message." + let request = UNNotificationRequest(identifier: "replyFailure", content: content, trigger: nil) + UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) + return } } - } else { - let body = RNNotificationParser.parseNotificationResponse(response) - RNEventEmitter.sendEvent(RNNotificationOpened, body: body) } } } diff --git a/ios/RocketChatRN-Bridging-Header.h b/ios/RocketChatRN-Bridging-Header.h index 32825b2b635..40e9146f471 100644 --- a/ios/RocketChatRN-Bridging-Header.h +++ b/ios/RocketChatRN-Bridging-Header.h @@ -5,12 +5,6 @@ #import "SecureStorage.h" #import "MMKVKeyManager.h" #import "Shared/RocketChat/MMKVBridge.h" -#import -#import -#import -#import -#import -#import #import #import #import diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index e510680542b..c901848d451 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0745F30D29A18A45DDDF8568 /* Pods_defaults_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5DA03EEFD8CEA0E9578CEFA /* Pods_defaults_NotificationService.framework */; }; 0C6E2DE448364EA896869ADF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B37C79D9BD0742CE936B6982 /* libc++.tbd */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 1E01C81C2511208400FEF824 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C81B2511208400FEF824 /* URL+Extensions.swift */; }; @@ -278,8 +279,6 @@ 3F56D232A9EBA1C9C749F15F /* MMKVBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B215A44CFB843397273C7EC /* MMKVBridge.mm */; }; 3F56D232A9EBA1C9C749F160 /* MMKVBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B215A44CFB843397273C7EC /* MMKVBridge.mm */; }; 4C4C8603EF082F0A33A95522 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */; }; - 542B26E977F9D0D645359412 /* Pods_defaults_RocketChatRN.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E468595F0CACBBB30640F7D /* Pods_defaults_RocketChatRN.framework */; }; - 600F30C06FDB7188A8FCA452 /* Pods_defaults_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2BF76DA1450703CDCF8A4C42 /* Pods_defaults_NotificationService.framework */; }; 65AD38372BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD38392BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD383A2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; @@ -360,13 +359,14 @@ 7ACFE7DA2DDE48760090D9BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACFE7D82DDE48760090D9BC /* AppDelegate.swift */; }; 7AE10C0628A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 7AE10C0828A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; + 815F9657A87D16E93AD8451E /* Pods_defaults_Rocket_Chat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF268DB43E067211CC4AB1D8 /* Pods_defaults_Rocket_Chat.framework */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; + 8BC28DFD84976599F4DD0E1F /* Pods_defaults_RocketChatRN.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C9D354D4BED64C03F5586CC /* Pods_defaults_RocketChatRN.framework */; }; A2C6E2DD38F8BEE19BFB2E1D /* SecureStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B215A42CFB843397273C7EA /* SecureStorage.m */; }; A48B46D92D3FFBD200945489 /* A11yFlowModule.m in Sources */ = {isa = PBXBuildFile; fileRef = A48B46D82D3FFBD200945489 /* A11yFlowModule.m */; }; A48B46DA2D3FFBD200945489 /* A11yFlowModule.m in Sources */ = {isa = PBXBuildFile; fileRef = A48B46D82D3FFBD200945489 /* A11yFlowModule.m */; }; BC404914E86821389EEB543D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */; }; DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA7E862283664608B3894E34 /* libWatermelonDB.a */; }; - EE08C45FED0B9C53C0951564 /* Pods_defaults_Rocket_Chat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB48D08AB9265FA119D88B83 /* Pods_defaults_Rocket_Chat.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -612,11 +612,9 @@ 1EFEB5972493B6640072EDC0 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 1EFEB5992493B6640072EDC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1EFEB5A12493B67D0072EDC0 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; - 265A77689414BCF7977F3E07 /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = ""; }; - 2BF76DA1450703CDCF8A4C42 /* Pods_defaults_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 338E74D5495AE57AB52BC764 /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = ""; }; 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-Rocket.Chat/ExpoModulesProvider.swift"; sourceTree = ""; }; 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-RocketChatRN/ExpoModulesProvider.swift"; sourceTree = ""; }; + 4C9D354D4BED64C03F5586CC /* Pods_defaults_RocketChatRN.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_RocketChatRN.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 60B2A6A31FC4588700BD58E5 /* RocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RocketChatRN.entitlements; path = RocketChatRN/RocketChatRN.entitlements; sourceTree = ""; }; 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 65B9A7192AFC24190088956F /* ringtone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringtone.mp3; sourceTree = ""; }; @@ -624,15 +622,14 @@ 66C2701A2EBBCB570062725F /* MMKVKeyManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MMKVKeyManager.mm; sourceTree = ""; }; 66C2701E2EBBCB780062725F /* SecureStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecureStorage.h; sourceTree = ""; }; 66C2701F2EBBCB780062725F /* SecureStorage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SecureStorage.m; sourceTree = ""; }; - 6E468595F0CACBBB30640F7D /* Pods_defaults_RocketChatRN.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_RocketChatRN.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6F50F2B44849C3F5AF549047 /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = ""; }; - 72BE2DC82482F10567C83FD7 /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = ""; }; + 7065C6880465E9A8735AA5EF /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = ""; }; 7A006F13229C83B600803143 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 7A0129D22C6E8B5900F84A97 /* ShareRocketChatRN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareRocketChatRN.swift; sourceTree = ""; }; 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 7A14FCEC257FEB3A005BDCD4 /* Experimental.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Experimental.xcassets; sourceTree = ""; }; 7A14FCF3257FEB59005BDCD4 /* Official.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Official.xcassets; sourceTree = ""; }; 7A610CD127ECE38100B8ABDD /* custom.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = custom.ttf; sourceTree = ""; }; + 7A6B8ACA1953C727CACE14EB /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = ""; }; 7A8B30742BCD9D3F00146A40 /* SSLPinning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSLPinning.h; sourceTree = ""; }; 7A8B30752BCD9D3F00146A40 /* SSLPinning.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SSLPinning.mm; sourceTree = ""; }; 7AAA749C23043B1D00F1ADE9 /* RocketChatRN-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RocketChatRN-Bridging-Header.h"; sourceTree = ""; }; @@ -648,10 +645,13 @@ A48B46D82D3FFBD200945489 /* A11yFlowModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = A11yFlowModule.m; sourceTree = ""; }; B179038FDD7AAF285047814B /* SecureStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = SecureStorage.h; sourceTree = ""; }; B37C79D9BD0742CE936B6982 /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + B5DA03EEFD8CEA0E9578CEFA /* Pods_defaults_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BA7E862283664608B3894E34 /* libWatermelonDB.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libWatermelonDB.a; sourceTree = ""; }; - E508F5F0D3A212E54C74BC1D /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = ""; }; - EB48D08AB9265FA119D88B83 /* Pods_defaults_Rocket_Chat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_Rocket_Chat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EC3A37AC948D1CBA680F8A21 /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = ""; }; + CC5834318D0A8AF03D8124DB /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = ""; }; + CF268DB43E067211CC4AB1D8 /* Pods_defaults_Rocket_Chat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_Rocket_Chat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E023B58716C64D2BFB8C0681 /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = ""; }; + E06AA2822D8D24C3AA3C8711 /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = ""; }; + F35C8301F7A5B8286AC64516 /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -672,7 +672,7 @@ 7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */, 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */, DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */, - 542B26E977F9D0D645359412 /* Pods_defaults_RocketChatRN.framework in Frameworks */, + 8BC28DFD84976599F4DD0E1F /* Pods_defaults_RocketChatRN.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -694,7 +694,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 600F30C06FDB7188A8FCA452 /* Pods_defaults_NotificationService.framework in Frameworks */, + 0745F30D29A18A45DDDF8568 /* Pods_defaults_NotificationService.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -715,7 +715,7 @@ 7AAB3E3D257E6A6E00707CF6 /* JavaScriptCore.framework in Frameworks */, 7AAB3E3E257E6A6E00707CF6 /* libz.tbd in Frameworks */, 7AAB3E3F257E6A6E00707CF6 /* libWatermelonDB.a in Frameworks */, - EE08C45FED0B9C53C0951564 /* Pods_defaults_Rocket_Chat.framework in Frameworks */, + 815F9657A87D16E93AD8451E /* Pods_defaults_Rocket_Chat.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1143,12 +1143,12 @@ 7AC2B09613AA7C3FEBAC9F57 /* Pods */ = { isa = PBXGroup; children = ( - 6F50F2B44849C3F5AF549047 /* Pods-defaults-NotificationService.debug.xcconfig */, - E508F5F0D3A212E54C74BC1D /* Pods-defaults-NotificationService.release.xcconfig */, - 338E74D5495AE57AB52BC764 /* Pods-defaults-Rocket.Chat.debug.xcconfig */, - 72BE2DC82482F10567C83FD7 /* Pods-defaults-Rocket.Chat.release.xcconfig */, - 265A77689414BCF7977F3E07 /* Pods-defaults-RocketChatRN.debug.xcconfig */, - EC3A37AC948D1CBA680F8A21 /* Pods-defaults-RocketChatRN.release.xcconfig */, + 7A6B8ACA1953C727CACE14EB /* Pods-defaults-NotificationService.debug.xcconfig */, + E06AA2822D8D24C3AA3C8711 /* Pods-defaults-NotificationService.release.xcconfig */, + E023B58716C64D2BFB8C0681 /* Pods-defaults-Rocket.Chat.debug.xcconfig */, + 7065C6880465E9A8735AA5EF /* Pods-defaults-Rocket.Chat.release.xcconfig */, + CC5834318D0A8AF03D8124DB /* Pods-defaults-RocketChatRN.debug.xcconfig */, + F35C8301F7A5B8286AC64516 /* Pods-defaults-RocketChatRN.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1247,9 +1247,9 @@ 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */, B37C79D9BD0742CE936B6982 /* libc++.tbd */, 06BB44DD4855498082A744AD /* libz.tbd */, - 2BF76DA1450703CDCF8A4C42 /* Pods_defaults_NotificationService.framework */, - EB48D08AB9265FA119D88B83 /* Pods_defaults_Rocket_Chat.framework */, - 6E468595F0CACBBB30640F7D /* Pods_defaults_RocketChatRN.framework */, + B5DA03EEFD8CEA0E9578CEFA /* Pods_defaults_NotificationService.framework */, + CF268DB43E067211CC4AB1D8 /* Pods_defaults_Rocket_Chat.framework */, + 4C9D354D4BED64C03F5586CC /* Pods_defaults_RocketChatRN.framework */, ); name = Frameworks; sourceTree = ""; @@ -1269,7 +1269,7 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RocketChatRN" */; buildPhases = ( - 601E2DCB3D2AE923C1934115 /* [CP] Check Pods Manifest.lock */, + C0B975AF6ED607297F8F55F4 /* [CP] Check Pods Manifest.lock */, 7AA5C63E23E30D110005C4A7 /* Start Packager */, 589729E8381BA997CD19EF19 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, @@ -1282,8 +1282,8 @@ 7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */, 407D3EDE3DABEE15D27BD87D /* ShellScript */, 9C104B12BEE385F7555E641F /* [Expo] Configure project */, - 83E5556DD496DD97055F0B21 /* [CP] Embed Pods Frameworks */, - CFB901605AECDF9D4106D152 /* [CP] Copy Pods Resources */, + 69EE0EAB4655CCB0698B6026 /* [CP] Embed Pods Frameworks */, + 4EF35507D275D88665224EED /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1351,12 +1351,12 @@ isa = PBXNativeTarget; buildConfigurationList = 1EFEB5A02493B6640072EDC0 /* Build configuration list for PBXNativeTarget "NotificationService" */; buildPhases = ( - 974065F792CC0D60E1F63EBD /* [CP] Check Pods Manifest.lock */, + EBDF1B5B8303C6FF72717B0B /* [CP] Check Pods Manifest.lock */, 86A998705576AFA7CE938617 /* [Expo] Configure project */, 1EFEB5912493B6640072EDC0 /* Sources */, 1EFEB5922493B6640072EDC0 /* Frameworks */, 1EFEB5932493B6640072EDC0 /* Resources */, - 8F403F7E8D286C6AB01BD57C /* [CP] Copy Pods Resources */, + B4801301A00C50FA3AD72CF9 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1371,7 +1371,7 @@ isa = PBXNativeTarget; buildConfigurationList = 7AAB3E4F257E6A6E00707CF6 /* Build configuration list for PBXNativeTarget "Rocket.Chat" */; buildPhases = ( - D03AD0E345B1F71DFB6BF874 /* [CP] Check Pods Manifest.lock */, + C32210C70D1F9214A2DE8E19 /* [CP] Check Pods Manifest.lock */, 7AAB3E13257E6A6E00707CF6 /* Start Packager */, 84028E94C77DEBDD5200728D /* [Expo] Configure project */, 7AAB3E14257E6A6E00707CF6 /* Sources */, @@ -1382,8 +1382,8 @@ 7AAB3E4B257E6A6E00707CF6 /* ShellScript */, 1ED1ECE32B8699DD00F6620C /* Embed Watch Content */, 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */, - 2DAD6E48B817688F7DC701E4 /* [CP] Embed Pods Frameworks */, - A21A863DFE0C97848CFA22B1 /* [CP] Copy Pods Resources */, + F55B2F4877AB3302D8608673 /* [CP] Embed Pods Frameworks */, + 7B5EE97580C4626E59AEA53C /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1555,7 +1555,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node \n../node_modules/react-native/scripts/react-native-xcode.sh\n"; + shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; 1E1EA8082326CCE300E22452 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -1574,40 +1574,98 @@ shellPath = /bin/sh; shellScript = "echo \"Target architectures: $ARCHS\"\n\nAPP_PATH=\"${TARGET_BUILD_DIR}/${WRAPPER_NAME}\"\n\nfind \"$APP_PATH\" -name '*.framework' -type d | while read -r FRAMEWORK\ndo\nFRAMEWORK_EXECUTABLE_NAME=$(defaults read \"$FRAMEWORK/Info.plist\" CFBundleExecutable)\nFRAMEWORK_EXECUTABLE_PATH=\"$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME\"\necho \"Executable is $FRAMEWORK_EXECUTABLE_PATH\"\necho $(lipo -info \"$FRAMEWORK_EXECUTABLE_PATH\")\n\nFRAMEWORK_TMP_PATH=\"$FRAMEWORK_EXECUTABLE_PATH-tmp\"\n\n# remove simulator's archs if location is not simulator's directory\ncase \"${TARGET_BUILD_DIR}\" in\n*\"iphonesimulator\")\necho \"No need to remove archs\"\n;;\n*)\nif $(lipo \"$FRAMEWORK_EXECUTABLE_PATH\" -verify_arch \"i386\") ; then\nlipo -output \"$FRAMEWORK_TMP_PATH\" -remove \"i386\" \"$FRAMEWORK_EXECUTABLE_PATH\"\necho \"i386 architecture removed\"\nrm \"$FRAMEWORK_EXECUTABLE_PATH\"\nmv \"$FRAMEWORK_TMP_PATH\" \"$FRAMEWORK_EXECUTABLE_PATH\"\nfi\nif $(lipo \"$FRAMEWORK_EXECUTABLE_PATH\" -verify_arch \"x86_64\") ; then\nlipo -output \"$FRAMEWORK_TMP_PATH\" -remove \"x86_64\" \"$FRAMEWORK_EXECUTABLE_PATH\"\necho \"x86_64 architecture removed\"\nrm \"$FRAMEWORK_EXECUTABLE_PATH\"\nmv \"$FRAMEWORK_TMP_PATH\" \"$FRAMEWORK_EXECUTABLE_PATH\"\nfi\n;;\nesac\n\necho \"Completed for executable $FRAMEWORK_EXECUTABLE_PATH\"\necho $\n\ndone\n"; }; - 2DAD6E48B817688F7DC701E4 /* [CP] Embed Pods Frameworks */ = { + 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { + 4EF35507D275D88665224EED /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/BugsnagReactNative/Bugsnag.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Bugsnag.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; showEnvVarsInLog = 0; }; 589729E8381BA997CD19EF19 /* [Expo] Configure project */ = { @@ -1629,26 +1687,22 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-RocketChatRN/expo-configure-project.sh\"\n"; }; - 601E2DCB3D2AE923C1934115 /* [CP] Check Pods Manifest.lock */ = { + 69EE0EAB4655CCB0698B6026 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */ = { @@ -1659,7 +1713,7 @@ inputFileListPaths = ( ); inputPaths = ( - $TARGET_BUILD_DIR/$INFOPLIST_PATH, + "$TARGET_BUILD_DIR/$INFOPLIST_PATH", ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( @@ -1745,7 +1799,7 @@ inputFileListPaths = ( ); inputPaths = ( - $TARGET_BUILD_DIR/$INFOPLIST_PATH, + "$TARGET_BUILD_DIR/$INFOPLIST_PATH", ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( @@ -1756,72 +1810,19 @@ shellPath = /bin/sh; shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n"; }; - 83E5556DD496DD97055F0B21 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 84028E94C77DEBDD5200728D /* [Expo] Configure project */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-Rocket.Chat/expo-configure-project.sh\"\n"; - }; - 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; - }; - 8F403F7E8D286C6AB01BD57C /* [CP] Copy Pods Resources */ = { + 7B5EE97580C4626E59AEA53C /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/BugsnagReactNative/Bugsnag.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", @@ -1850,8 +1851,11 @@ name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Bugsnag.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", @@ -1879,30 +1883,46 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 974065F792CC0D60E1F63EBD /* [CP] Check Pods Manifest.lock */ = { + 84028E94C77DEBDD5200728D /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "[Expo] Configure project"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-Rocket.Chat/expo-configure-project.sh\"\n"; + }; + 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; }; 9C104B12BEE385F7555E641F /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; @@ -1923,16 +1943,19 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; }; - A21A863DFE0C97848CFA22B1 /* [CP] Copy Pods Resources */ = { + B4801301A00C50FA3AD72CF9 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/BugsnagReactNative/Bugsnag.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", @@ -1961,8 +1984,11 @@ name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Bugsnag.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", @@ -1990,80 +2016,32 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; showEnvVarsInLog = 0; }; - CFB901605AECDF9D4106D152 /* [CP] Copy Pods Resources */ = { + C0B975AF6ED607297F8F55F4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/BugsnagReactNative/Bugsnag.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Bugsnag.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", + "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - D03AD0E345B1F71DFB6BF874 /* [CP] Check Pods Manifest.lock */ = { + C32210C70D1F9214A2DE8E19 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2085,6 +2063,46 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + EBDF1B5B8303C6FF72717B0B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F55B2F4877AB3302D8608673 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -2474,7 +2492,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 265A77689414BCF7977F3E07 /* Pods-defaults-RocketChatRN.debug.xcconfig */; + baseConfigurationReference = CC5834318D0A8AF03D8124DB /* Pods-defaults-RocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2539,7 +2557,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EC3A37AC948D1CBA680F8A21 /* Pods-defaults-RocketChatRN.release.xcconfig */; + baseConfigurationReference = F35C8301F7A5B8286AC64516 /* Pods-defaults-RocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2646,7 +2664,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - $PODS_CONFIGURATION_BUILD_DIR/Firebase, + "$PODS_CONFIGURATION_BUILD_DIR/Firebase", ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -2722,7 +2740,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - $PODS_CONFIGURATION_BUILD_DIR/Firebase, + "$PODS_CONFIGURATION_BUILD_DIR/Firebase", ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -2954,7 +2972,7 @@ }; 1EFEB59D2493B6640072EDC0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6F50F2B44849C3F5AF549047 /* Pods-defaults-NotificationService.debug.xcconfig */; + baseConfigurationReference = 7A6B8ACA1953C727CACE14EB /* Pods-defaults-NotificationService.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3006,7 +3024,7 @@ }; 1EFEB59E2493B6640072EDC0 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E508F5F0D3A212E54C74BC1D /* Pods-defaults-NotificationService.release.xcconfig */; + baseConfigurationReference = E06AA2822D8D24C3AA3C8711 /* Pods-defaults-NotificationService.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3057,7 +3075,7 @@ }; 7AAB3E50257E6A6E00707CF6 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 338E74D5495AE57AB52BC764 /* Pods-defaults-Rocket.Chat.debug.xcconfig */; + baseConfigurationReference = E023B58716C64D2BFB8C0681 /* Pods-defaults-Rocket.Chat.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3122,7 +3140,7 @@ }; 7AAB3E51257E6A6E00707CF6 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 72BE2DC82482F10567C83FD7 /* Pods-defaults-Rocket.Chat.release.xcconfig */; + baseConfigurationReference = 7065C6880465E9A8735AA5EF /* Pods-defaults-Rocket.Chat.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3247,7 +3265,10 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -3311,7 +3332,10 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/ios/Shared/Models/NotificationType.swift b/ios/Shared/Models/NotificationType.swift index 95e41bed78c..0e769aa0910 100644 --- a/ios/Shared/Models/NotificationType.swift +++ b/ios/Shared/Models/NotificationType.swift @@ -11,4 +11,5 @@ import Foundation enum NotificationType: String, Codable { case message = "message" case messageIdOnly = "message-id-only" + case videoconf = "videoconf" } diff --git a/ios/Shared/Models/Payload.swift b/ios/Shared/Models/Payload.swift index 124ccf612e6..2a91f3a1b38 100644 --- a/ios/Shared/Models/Payload.swift +++ b/ios/Shared/Models/Payload.swift @@ -8,12 +8,17 @@ import Foundation +struct Caller: Codable { + let _id: String? + let name: String? +} + struct Payload: Codable { let host: String let rid: String? let type: RoomType? let sender: Sender? - let messageId: String + let messageId: String? let notificationType: NotificationType? let name: String? let messageType: MessageType? @@ -21,4 +26,9 @@ struct Payload: Codable { let senderName: String? let tmid: String? let content: EncryptedContent? + + // Video conference fields + let caller: Caller? + let callId: String? + let status: Int? } diff --git a/ios/Shared/RocketChat/API/Request.swift b/ios/Shared/RocketChat/API/Request.swift index a1f6a728c57..97c54bf8574 100644 --- a/ios/Shared/RocketChat/API/Request.swift +++ b/ios/Shared/RocketChat/API/Request.swift @@ -55,6 +55,7 @@ extension Request { request.httpMethod = method.rawValue request.httpBody = body() request.addValue(contentType, forHTTPHeaderField: "Content-Type") + request.addValue(userAgent, forHTTPHeaderField: "User-Agent") if let userId = api.credentials?.userId { request.addValue(userId, forHTTPHeaderField: "x-user-id") @@ -69,4 +70,14 @@ extension Request { return request } + + private var userAgent: String { + let osVersion = ProcessInfo.processInfo.operatingSystemVersion + let systemVersion = "\(osVersion.majorVersion).\(osVersion.minorVersion)" + + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" + let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "unknown" + + return "RC Mobile; ios \(systemVersion); v\(appVersion) (\(buildNumber))" + } } diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 2d8930b0f00..80a43c7f587 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -136,7 +136,7 @@ platform :ios do build_path: "./fastlane/build", configuration: "Release", derived_data_path: "./fastlane/derived_data", - xcargs: "-parallelizeTargets -jobs 4" + xcargs: "-parallelizeTargets -jobs 4 ARCHS=arm64" ) end diff --git a/jest.setup.js b/jest.setup.js index 6374b2beb62..774f02d2527 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -136,18 +136,23 @@ jest.mock('@react-navigation/native', () => { }; }); -jest.mock('react-native-notifications', () => ({ - Notifications: { - getInitialNotification: jest.fn(() => Promise.resolve()), - registerRemoteNotifications: jest.fn(), - events: () => ({ - registerRemoteNotificationsRegistered: jest.fn(), - registerRemoteNotificationsRegistrationFailed: jest.fn(), - registerNotificationReceivedForeground: jest.fn(), - registerNotificationReceivedBackground: jest.fn(), - registerNotificationOpened: jest.fn() - }) - } +jest.mock('expo-notifications', () => ({ + getDevicePushTokenAsync: jest.fn(() => Promise.resolve({ data: 'mock-token' })), + getPermissionsAsync: jest.fn(() => Promise.resolve({ status: 'granted' })), + requestPermissionsAsync: jest.fn(() => Promise.resolve({ status: 'granted' })), + setBadgeCountAsync: jest.fn(() => Promise.resolve(true)), + dismissAllNotificationsAsync: jest.fn(() => Promise.resolve()), + setNotificationHandler: jest.fn(), + setNotificationCategoryAsync: jest.fn(() => Promise.resolve()), + addNotificationReceivedListener: jest.fn(() => ({ remove: jest.fn() })), + addNotificationResponseReceivedListener: jest.fn(() => ({ remove: jest.fn() })), + addPushTokenListener: jest.fn(() => ({ remove: jest.fn() })), + getLastNotificationResponse: jest.fn(() => null), + DEFAULT_ACTION_IDENTIFIER: 'expo.modules.notifications.actions.DEFAULT' +})); + +jest.mock('expo-device', () => ({ + isDevice: true })); jest.mock('@discord/bottom-sheet', () => { diff --git a/package.json b/package.json index 0b25573a0b4..2a8e7f22435 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", "@expo/vector-icons": "^14.1.0", "@hookform/resolvers": "^2.9.10", - "@notifee/react-native": "7.8.2", "@nozbe/watermelondb": "^0.28.1-0", "@react-native-async-storage/async-storage": "^1.22.3", "@react-native-camera-roll/camera-roll": "^7.10.0", @@ -41,7 +40,6 @@ "@react-native-firebase/analytics": "^21.12.2", "@react-native-firebase/app": "^21.12.2", "@react-native-firebase/crashlytics": "^21.12.2", - "@react-native-firebase/messaging": "^21.12.2", "@react-native-masked-view/masked-view": "^0.3.1", "@react-native-picker/picker": "^2.11.0", "@react-native/codegen": "^0.80.0", @@ -63,6 +61,7 @@ "expo-apple-authentication": "7.2.3", "expo-av": "15.1.3", "expo-camera": "16.1.5", + "expo-device": "^8.0.10", "expo-document-picker": "13.1.4", "expo-file-system": "18.1.7", "expo-haptics": "14.1.3", @@ -70,6 +69,7 @@ "expo-keep-awake": "14.1.3", "expo-local-authentication": "16.0.3", "expo-navigation-bar": "^4.2.4", + "expo-notifications": "0.32.11", "expo-status-bar": "^2.2.3", "expo-system-ui": "^5.0.7", "expo-video-thumbnails": "9.1.2", @@ -107,7 +107,6 @@ "react-native-mime-types": "2.3.0", "react-native-mmkv": "3.3.3", "react-native-modal": "13.0.1", - "react-native-notifications": "5.1.0", "react-native-notifier": "1.6.1", "react-native-picker-select": "9.0.1", "react-native-platform-touchable": "1.1.1", diff --git a/patches/@notifee+react-native+7.8.2.patch b/patches/@notifee+react-native+7.8.2.patch deleted file mode 100644 index cdb17b7853a..00000000000 --- a/patches/@notifee+react-native+7.8.2.patch +++ /dev/null @@ -1,41 +0,0 @@ ---- a/node_modules/@notifee/react-native/android/src/main/java/io/invertase/notifee/NotifeeApiModule.java -+++ b/node_modules/@notifee/react-native/android/src/main/java/io/invertase/notifee/NotifeeApiModule.java -@@ -238,7 +238,7 @@ public class NotifeeApiModule extends ReactContextBaseJavaModule implements Perm - @ReactMethod - public void requestPermission(Promise promise) { - // For Android 12 and below, we return the notification settings -- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { -+ if (Build.VERSION.SDK_INT < 33) { - Notifee.getInstance() - .getNotificationSettings( - (e, aBundle) -> NotifeeReactUtils.promiseResolver(promise, e, aBundle)); -@@ -265,7 +265,7 @@ public class NotifeeApiModule extends ReactContextBaseJavaModule implements Perm - (e, aBundle) -> NotifeeReactUtils.promiseResolver(promise, e, aBundle)); - - activity.requestPermissions( -- new String[] {Manifest.permission.POST_NOTIFICATIONS}, -+ new String[] {"android.permission.POST_NOTIFICATIONS"}, - Notifee.REQUEST_CODE_NOTIFICATION_PERMISSION, - this); - } -diff --git a/node_modules/@notifee/react-native/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m b/node_modules/@notifee/react-native/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m -index cf8020d..3a1e080 100644 ---- a/node_modules/@notifee/react-native/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m -+++ b/node_modules/@notifee/react-native/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m -@@ -179,11 +179,11 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center - - _notificationOpenedAppID = notifeeNotification[@"id"]; - -- // handle notification outside of notifee -- if (notifeeNotification == nil) { -- notifeeNotification = -- [NotifeeCoreUtil parseUNNotificationRequest:response.notification.request]; -- } -+ // disable notifee handler on ios devices -+ // if (notifeeNotification == nil) { -+ // notifeeNotification = -+ // [NotifeeCoreUtil parseUNNotificationRequest:response.notification.request]; -+ // } - - if (notifeeNotification != nil) { - if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) { diff --git a/patches/@react-native-firebase+messaging+21.12.2.patch b/patches/@react-native-firebase+messaging+21.12.2.patch deleted file mode 100644 index 34905325f15..00000000000 --- a/patches/@react-native-firebase+messaging+21.12.2.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/node_modules/@react-native-firebase/messaging/android/src/main/AndroidManifest.xml b/node_modules/@react-native-firebase/messaging/android/src/main/AndroidManifest.xml -index 2f6741b..d4f4abb 100644 ---- a/node_modules/@react-native-firebase/messaging/android/src/main/AndroidManifest.xml -+++ b/node_modules/@react-native-firebase/messaging/android/src/main/AndroidManifest.xml -@@ -9,12 +9,12 @@ - - -- - - - -- -+ --> - --@import UserNotifications; -+#import - - @interface RCTConvert (UIMutableUserNotificationAction) - + (UIMutableUserNotificationAction *)UIMutableUserNotificationAction:(id)json; -diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.h b/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.h -index 4bc5292..71df0bc 100644 ---- a/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.h -+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.h -@@ -4,7 +4,7 @@ typedef void (^RCTPromiseResolveBlock)(id result); - typedef void (^RCTResponseSenderBlock)(NSArray *response); - typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error); - --@import UserNotifications; -+#import - - @interface RNNotificationCenter : NSObject - -diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.m b/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.m -index afd5c73..ec4dd85 100644 ---- a/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.m -+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationCenter.m -@@ -48,14 +48,15 @@ - } - - - (void)setCategories:(NSArray *)json { -- NSMutableSet* categories = nil; -+ NSMutableSet* categories = [NSMutableSet new]; - -- if ([json count] > 0) { -- categories = [NSMutableSet new]; -- for (NSDictionary* categoryJson in json) { -- [categories addObject:[RCTConvert UNMutableUserNotificationCategory:categoryJson]]; -+ for (NSDictionary* categoryJson in json) { -+ UNNotificationCategory *category = [RCTConvert UNMutableUserNotificationCategory:categoryJson]; -+ if (category) { -+ [categories addObject:category]; - } - } -+ - [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categories]; - } - -diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationCenterListener.h b/node_modules/react-native-notifications/lib/ios/RNNotificationCenterListener.h -index 77a67dd..eaf0043 100644 ---- a/node_modules/react-native-notifications/lib/ios/RNNotificationCenterListener.h -+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationCenterListener.h -@@ -1,5 +1,5 @@ - #import --@import UserNotifications; -+#import - #import "RNNotificationEventHandler.h" - - @interface RNNotificationCenterListener : NSObject -diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationEventHandler.h b/node_modules/react-native-notifications/lib/ios/RNNotificationEventHandler.h -index a07c6e9..8e3ca6a 100644 ---- a/node_modules/react-native-notifications/lib/ios/RNNotificationEventHandler.h -+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationEventHandler.h -@@ -1,5 +1,5 @@ - #import --@import UserNotifications; -+#import - #import "RNNotificationsStore.h" - #import "RNEventEmitter.h" - -diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationParser.h b/node_modules/react-native-notifications/lib/ios/RNNotificationParser.h -index 7aa2bfb..c1c019c 100644 ---- a/node_modules/react-native-notifications/lib/ios/RNNotificationParser.h -+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationParser.h -@@ -1,5 +1,5 @@ - #import --@import UserNotifications; -+#import - - @interface RNNotificationParser : NSObject - -diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationParser.m b/node_modules/react-native-notifications/lib/ios/RNNotificationParser.m -index 62f3043..840acda 100644 ---- a/node_modules/react-native-notifications/lib/ios/RNNotificationParser.m -+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationParser.m -@@ -12,7 +12,18 @@ - } - - + (NSDictionary *)parseNotificationResponse:(UNNotificationResponse *)response { -- NSDictionary* responseDict = @{@"notification": [RCTConvert UNNotificationPayload:response.notification], @"identifier": response.notification.request.identifier, @"action": [self parseNotificationResponseAction:response]}; -+ NSMutableDictionary *notificationPayload = [[RCTConvert UNNotificationPayload:response.notification] mutableCopy]; -+ -+ NSDictionary *responseAction = [self parseNotificationResponseAction:response]; -+ -+ if (responseAction != nil) { -+ [notificationPayload setObject:responseAction forKey:@"action"]; -+ } -+ -+ NSDictionary *responseDict = @{ -+ @"notification": [notificationPayload copy], -+ @"identifier": response.notification.request.identifier -+ }; - - return responseDict; - } -diff --git a/node_modules/react-native-notifications/lib/ios/RNNotificationsStore.h b/node_modules/react-native-notifications/lib/ios/RNNotificationsStore.h -index 4f8a171..7e4f9ca 100644 ---- a/node_modules/react-native-notifications/lib/ios/RNNotificationsStore.h -+++ b/node_modules/react-native-notifications/lib/ios/RNNotificationsStore.h -@@ -1,6 +1,6 @@ - #import - #import --@import UserNotifications; -+#import - - @interface RNNotificationsStore : NSObject - diff --git a/react-native.config.js b/react-native.config.js index 685aec05f4b..5af09eb7525 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -1,9 +1,3 @@ module.exports = { - dependencies: { - '@react-native-firebase/messaging': { - platforms: { - ios: null - } - } - } + dependencies: {} }; diff --git a/yarn.lock b/yarn.lock index 616e8c9ab37..5f334cbad37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2750,6 +2750,26 @@ xcode "^3.0.1" xml2js "0.6.0" +"@expo/config-plugins@~54.0.3": + version "54.0.3" + resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-54.0.3.tgz#2b9ffd68a48e3b51299cdbe3ee777b9f5163fc03" + integrity sha512-tBIUZIxLQfCu5jmqTO+UOeeDUGIB0BbK6xTMkPRObAXRQeTLPPfokZRCo818d2owd+Bcmq1wBaDz0VY3g+glfw== + dependencies: + "@expo/config-types" "^54.0.9" + "@expo/json-file" "~10.0.7" + "@expo/plist" "^0.4.7" + "@expo/sdk-runtime-versions" "^1.0.0" + chalk "^4.1.2" + debug "^4.3.5" + getenv "^2.0.0" + glob "^13.0.0" + resolve-from "^5.0.0" + semver "^7.5.4" + slash "^3.0.0" + slugify "^1.6.6" + xcode "^3.0.1" + xml2js "0.6.0" + "@expo/config-types@^53.0.3": version "53.0.3" resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-53.0.3.tgz#d083d9b095972e89eee96c41d085feb5b92d2749" @@ -2760,6 +2780,16 @@ resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-53.0.4.tgz#fe64fac734531ae883d18529b32586c23ffb1ceb" integrity sha512-0s+9vFx83WIToEr0Iwy4CcmiUXa5BgwBmEjylBB2eojX5XAMm9mJvw9KpjAb8m7zq2G0Q6bRbeufkzgbipuNQg== +"@expo/config-types@^54.0.10": + version "54.0.10" + resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-54.0.10.tgz#688f4338255d2fdea970f44e2dfd8e8d37dec292" + integrity sha512-/J16SC2an1LdtCZ67xhSkGXpALYUVUNyZws7v+PVsFZxClYehDSoKLqyRaGkpHlYrCc08bS0RF5E0JV6g50psA== + +"@expo/config-types@^54.0.9": + version "54.0.9" + resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-54.0.9.tgz#b9279c47fe249b774fbd3358b6abddea08f1bcec" + integrity sha512-Llf4jwcrAnrxgE5WCdAOxtMf8FGwS4Sk0SSgI0NnIaSyCnmOCAm80GPFvsK778Oj19Ub4tSyzdqufPyeQPksWw== + "@expo/config@~11.0.7", "@expo/config@~11.0.8": version "11.0.8" resolved "https://registry.yarnpkg.com/@expo/config/-/config-11.0.8.tgz#658538d4321cf6edf6741f8b8506fda0046d5e94" @@ -2798,6 +2828,25 @@ slugify "^1.3.4" sucrase "3.35.0" +"@expo/config@~12.0.12": + version "12.0.12" + resolved "https://registry.yarnpkg.com/@expo/config/-/config-12.0.12.tgz#5e26390ec209ee56432e980451c08b361e0f07dd" + integrity sha512-X2MW86+ulLpMGvdgfvpl2EOBAKUlwvnvoPwdaZeeyWufGopn1nTUeh4C9gMsplDaP1kXv9sLXVhOoUoX6g9PvQ== + dependencies: + "@babel/code-frame" "~7.10.4" + "@expo/config-plugins" "~54.0.3" + "@expo/config-types" "^54.0.10" + "@expo/json-file" "^10.0.8" + deepmerge "^4.3.1" + getenv "^2.0.0" + glob "^13.0.0" + require-from-string "^2.0.2" + resolve-from "^5.0.0" + resolve-workspace-root "^2.0.0" + semver "^7.6.0" + slugify "^1.3.4" + sucrase "~3.35.1" + "@expo/devcert@^1.1.2": version "1.1.4" resolved "https://registry.yarnpkg.com/@expo/devcert/-/devcert-1.1.4.tgz#d98807802a541847cc42791a606bfdc26e641277" @@ -2827,6 +2876,17 @@ dotenv-expand "~11.0.6" getenv "^1.0.0" +"@expo/env@~2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@expo/env/-/env-2.0.8.tgz#2aea906eed3d297b2e19608dc1a800fba0a3fe03" + integrity sha512-5VQD6GT8HIMRaSaB5JFtOXuvfDVU80YtZIuUT/GDhUF782usIXY13Tn3IdDz1Tm/lqA9qnRZQ1BF4t7LlvdJPA== + dependencies: + chalk "^4.0.0" + debug "^4.3.4" + dotenv "~16.4.5" + dotenv-expand "~11.0.6" + getenv "^2.0.0" + "@expo/fingerprint@0.12.4": version "0.12.4" resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.12.4.tgz#d4cc4de50e7b6d4e03b0d38850d1e4a136b74c8c" @@ -2858,6 +2918,30 @@ temp-dir "~2.0.0" unique-string "~2.0.0" +"@expo/image-utils@^0.8.7": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.8.8.tgz#db5d460fd0c7101b10e9d027ffbe42f9cf115248" + integrity sha512-HHHaG4J4nKjTtVa1GG9PCh763xlETScfEyNxxOvfTRr8IKPJckjTyqSLEtdJoFNJ1vqiABEjW7tqGhqGibZLeA== + dependencies: + "@expo/spawn-async" "^1.7.2" + chalk "^4.0.0" + getenv "^2.0.0" + jimp-compact "0.16.1" + parse-png "^2.1.0" + resolve-from "^5.0.0" + resolve-global "^1.0.0" + semver "^7.6.0" + temp-dir "~2.0.0" + unique-string "~2.0.0" + +"@expo/json-file@^10.0.8", "@expo/json-file@~10.0.7": + version "10.0.8" + resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-10.0.8.tgz#05e524d1ecc0011db0a6d66b525ea2f58cfe6d43" + integrity sha512-9LOTh1PgKizD1VXfGQ88LtDH0lRwq9lsTb4aichWTWSWqy3Ugfkhfm3BhzBIkJJfQQ5iJu3m/BoRlEIjoCGcnQ== + dependencies: + "@babel/code-frame" "~7.10.4" + json5 "^2.2.3" + "@expo/json-file@^9.1.4", "@expo/json-file@~9.1.4": version "9.1.4" resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-9.1.4.tgz#e719d092c08afb3234643f9285e57c6a24989327" @@ -2920,6 +3004,15 @@ base64-js "^1.2.3" xmlbuilder "^15.1.1" +"@expo/plist@^0.4.7": + version "0.4.8" + resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.4.8.tgz#e014511a4a5008cf2b832b91caa8e9f2704127cc" + integrity sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ== + dependencies: + "@xmldom/xmldom" "^0.8.8" + base64-js "^1.2.3" + xmlbuilder "^15.1.1" + "@expo/prebuild-config@^9.0.5": version "9.0.5" resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-9.0.5.tgz#b8b864b5e19489a1f66442ae30d5d7295f658297" @@ -3433,6 +3526,23 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@ide/backoff@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@ide/backoff/-/backoff-1.0.0.tgz#466842c25bd4a4833e0642fab41ccff064010176" + integrity sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g== + +"@isaacs/balanced-match@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" + integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== + +"@isaacs/brace-expansion@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" + integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== + dependencies: + "@isaacs/balanced-match" "^4.0.1" + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -3825,11 +3935,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@notifee/react-native@7.8.2": - version "7.8.2" - resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.8.2.tgz#72d3199ae830b4128ddaef3c1c2f11604759c9c4" - integrity sha512-VG4IkWJIlOKqXwa3aExC3WFCVCGCC9BA55Ivg0SMRfEs+ruvYy/zlLANcrVGiPtgkUEryXDhA8SXx9+JcO8oLA== - "@nozbe/simdjson@3.9.4": version "3.9.4" resolved "https://registry.yarnpkg.com/@nozbe/simdjson/-/simdjson-3.9.4.tgz#64bb522c54cd22e40ff4a64d8f8e8e9285b123b6" @@ -4281,11 +4386,6 @@ dependencies: stacktrace-js "^2.0.2" -"@react-native-firebase/messaging@^21.12.2": - version "21.12.2" - resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-21.12.2.tgz#9d8ec5dd21c562a6b5c5d3699fadde419c9f2a75" - integrity sha512-t/MQgqclINESO/1yCbgXTHJxAdxIzAnjtgTw6bj/po/1JRGroT+HG3VDpep9a3Z35S4f6eF90AE5zZzcKu8IjQ== - "@react-native-masked-view/masked-view@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.3.1.tgz#5bd76f17004a6ccbcec03856893777ee91f23d29" @@ -6123,6 +6223,17 @@ asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +assert@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd" + integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== + dependencies: + call-bind "^1.0.2" + is-nan "^1.3.2" + object-is "^1.1.5" + object.assign "^4.1.4" + util "^0.12.5" + assertion-error@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" @@ -6406,6 +6517,11 @@ babel-preset-jest@^29.6.3: babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" +badgin@^1.1.5: + version "1.2.3" + resolved "https://registry.yarnpkg.com/badgin/-/badgin-1.2.3.tgz#994b5f519827d7d5422224825b2c8faea2bc43ad" + integrity sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -6595,6 +6711,16 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply- es-errors "^1.3.0" function-bind "^1.1.2" +call-bind@^1.0.0, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -6606,16 +6732,6 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" -call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" @@ -8322,6 +8438,11 @@ expo-apple-authentication@7.2.3: resolved "https://registry.yarnpkg.com/expo-apple-authentication/-/expo-apple-authentication-7.2.3.tgz#524817c1b2c0b165343039d183ee91c4674be5fe" integrity sha512-2izNn8qhUUM/gXMxA2byOn4AymUpmhaZlnGZy1vpndT0dMXd3T/Wk8j67rEB0+JhQY11iEQGXBG8cfro7LV0dA== +expo-application@~7.0.7: + version "7.0.8" + resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-7.0.8.tgz#320af0d6c39b331456d3bc833b25763c702d23db" + integrity sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q== + expo-asset@~11.1.5: version "11.1.5" resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-11.1.5.tgz#5cad3d781c9d0edec31b9b3adbba574eb4d5dd3e" @@ -8350,6 +8471,21 @@ expo-constants@~17.1.5: "@expo/config" "~11.0.7" "@expo/env" "~1.0.5" +expo-constants@~18.0.8: + version "18.0.12" + resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-18.0.12.tgz#3e67f7109ffd03eaa5514c19875a767c39f5a2ca" + integrity sha512-WzcKYMVNRRu4NcSzfIVRD5aUQFnSpTZgXFrlWmm19xJoDa4S3/PQNi6PNTBRc49xz9h8FT7HMxRKaC8lr0gflA== + dependencies: + "@expo/config" "~12.0.12" + "@expo/env" "~2.0.8" + +expo-device@^8.0.10: + version "8.0.10" + resolved "https://registry.yarnpkg.com/expo-device/-/expo-device-8.0.10.tgz#88be854d6de5568392ed814b44dad0e19d1d50f8" + integrity sha512-jd5BxjaF7382JkDMaC+P04aXXknB2UhWaVx5WiQKA05ugm/8GH5uaz9P9ckWdMKZGQVVEOC8MHaUADoT26KmFA== + dependencies: + ua-parser-js "^0.7.33" + expo-document-picker@13.1.4: version "13.1.4" resolved "https://registry.yarnpkg.com/expo-document-picker/-/expo-document-picker-13.1.4.tgz#f78a91e31dfac8ff26ea065bdc015ce4938eb1cc" @@ -8429,6 +8565,19 @@ expo-navigation-bar@^4.2.4: react-native-edge-to-edge "1.6.0" react-native-is-edge-to-edge "^1.1.6" +expo-notifications@0.32.11: + version "0.32.11" + resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.32.11.tgz#0d90d08efdf4693ceaa32ab8bb7455d56424c441" + integrity sha512-4rLWC9Q4B7aQywXn9cKAlNY4p00CYKLJ23qZ0Pp/whkX0NxmI4MwJ20YhreV08gjHTTTWHpYU7jqYWpsjtPIxA== + dependencies: + "@expo/image-utils" "^0.8.7" + "@ide/backoff" "^1.0.0" + abort-controller "^3.0.0" + assert "^2.0.0" + badgin "^1.1.5" + expo-application "~7.0.7" + expo-constants "~18.0.8" + expo-status-bar@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-2.2.3.tgz#09385a866732328e0af3b4588c4f349a15fd7cd0" @@ -8936,6 +9085,11 @@ getenv@^1.0.0: resolved "https://registry.yarnpkg.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31" integrity sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg== +getenv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/getenv/-/getenv-2.0.0.tgz#b1698c7b0f29588f4577d06c42c73a5b475c69e0" + integrity sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ== + github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" @@ -8967,6 +9121,15 @@ glob@^10.3.10, glob@^10.4.2: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.0.tgz#9d9233a4a274fc28ef7adce5508b7ef6237a1be3" + integrity sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA== + dependencies: + minimatch "^10.1.1" + minipass "^7.1.2" + path-scurry "^2.0.0" + glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -8979,6 +9142,13 @@ glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg== + dependencies: + ini "^1.3.4" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -9384,7 +9554,7 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@~1.3.0: +ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -9562,6 +9732,14 @@ is-map@^2.0.3: resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== +is-nan@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-negative-zero@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" @@ -10784,6 +10962,11 @@ lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== +lru-cache@^11.0.0: + version "11.2.4" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.4.tgz#ecb523ebb0e6f4d837c807ad1abaea8e0619770d" + integrity sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg== + lru-cache@^4.1.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -11352,6 +11535,13 @@ minimatch@9.0.3: dependencies: brace-expansion "^2.0.1" +minimatch@^10.1.1: + version "10.1.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" + integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== + dependencies: + "@isaacs/brace-expansion" "^5.0.0" + minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -11639,6 +11829,14 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-is@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -11995,6 +12193,14 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-scurry@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.1.tgz#4b6572376cfd8b811fca9cd1f5c24b3cbac0fe10" + integrity sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -12562,11 +12768,6 @@ react-native-modal@13.0.1: prop-types "^15.6.2" react-native-animatable "1.3.3" -react-native-notifications@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/react-native-notifications/-/react-native-notifications-5.1.0.tgz#8cba105fd57ab9d5df9d27284acf1e2b4f3d7ea3" - integrity sha512-laqDSDlCvEASmJR6cXpqaryK855ejQd07vrfYERzhv68YDOoSkKy/URExRP4vAfAOVqHhix80tLbNUcfvZk2VQ== - react-native-notifier@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/react-native-notifier/-/react-native-notifier-1.6.1.tgz#eec07bdebed6c22cd22f5167555b7762e4119552" @@ -13100,6 +13301,13 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve-global@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-global/-/resolve-global-1.0.0.tgz#a2a79df4af2ca3f49bf77ef9ddacd322dad19255" + integrity sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw== + dependencies: + global-dirs "^0.1.1" + resolve-pkg-maps@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" @@ -14034,6 +14242,19 @@ sucrase@3.35.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" +sucrase@~3.35.1: + version "3.35.1" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.1.tgz#4619ea50393fe8bd0ae5071c26abd9b2e346bfe1" + integrity sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + tinyglobby "^0.2.11" + ts-interface-checker "^0.1.9" + sudo-prompt@^8.2.0: version "8.2.5" resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.5.tgz#cc5ef3769a134bb94b24a631cc09628d4d53603e" @@ -14237,7 +14458,7 @@ tiny-invariant@^1.3.3: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== -tinyglobby@^0.2.14: +tinyglobby@^0.2.11, tinyglobby@^0.2.14: version "0.2.15" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== @@ -14530,7 +14751,7 @@ typical@^5.2.0: resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== -ua-parser-js@1.0.2: +ua-parser-js@1.0.2, ua-parser-js@^0.7.33: version "1.0.2" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775" integrity sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg== @@ -14724,7 +14945,7 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@^0.12.4: +util@^0.12.4, util@^0.12.5: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==