diff --git a/.github/actions/upload-android/action.yml b/.github/actions/upload-android/action.yml
index 7f1fb418089..aafeccf437f 100644
--- a/.github/actions/upload-android/action.yml
+++ b/.github/actions/upload-android/action.yml
@@ -53,6 +53,26 @@ runs:
echo "${{ inputs.FASTLANE_GOOGLE_SERVICE_ACCOUNT }}" | base64 --decode > service_account.json
shell: bash
+ - uses: actions/download-artifact@v4
+ if: ${{ inputs.trigger == 'develop' }}
+ with:
+ name: release-changelog
+ path: .
+
+ - name: Prepare Play Store changelog metadata
+ if: ${{ inputs.trigger == 'develop' }}
+ run: |
+ mkdir -p android/fastlane/metadata/android/en-US/changelogs
+
+ if [ -f changelog.txt ]; then
+ node .github/scripts/prepare-changelog.js
+ else
+ printf "Internal improvements and bug fixes" > "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt"
+ fi
+ shell: bash
+ env:
+ BUILD_VERSION: ${{ inputs.BUILD_VERSION }}
+
- name: Fastlane Play Store Upload
working-directory: android
run: |
@@ -65,7 +85,6 @@ runs:
if [[ ${{ inputs.trigger }} == "develop" ]] && [[ ${{ inputs.type }} == 'official' ]]; then
bundle exec fastlane android official_open_testing
fi
-
shell: bash
- name: Leave a comment on PR
diff --git a/.github/actions/upload-ios/action.yml b/.github/actions/upload-ios/action.yml
index 68f5d4539d4..4deefc781a2 100644
--- a/.github/actions/upload-ios/action.yml
+++ b/.github/actions/upload-ios/action.yml
@@ -109,6 +109,12 @@ runs:
yarn pod-install
shell: bash
+ - uses: actions/download-artifact@v4
+ if: ${{ inputs.type == 'official' && inputs.trigger == 'develop' }}
+ with:
+ name: release-changelog
+ path: .
+
- name: Fastlane Submit to TestFlight
working-directory: ios
run: |
@@ -157,4 +163,4 @@ runs:
message="**iOS Build Available**"$'\n\n'"$app_name $VERSION_NAME.$BUILD_VERSION"
gh pr comment "$PR_NUMBER" --body "$message"
- shell: bash
\ No newline at end of file
+ shell: bash
diff --git a/.github/scripts/prepare-changelog.js b/.github/scripts/prepare-changelog.js
new file mode 100644
index 00000000000..9b31db56352
--- /dev/null
+++ b/.github/scripts/prepare-changelog.js
@@ -0,0 +1,20 @@
+const fs = require("fs");
+
+const buildVersion = process.env.BUILD_VERSION;
+const input = fs.readFileSync("changelog.txt", "utf8");
+
+const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
+const chars = Array.from(segmenter.segment(input), s => s.segment);
+
+let output;
+if (chars.length > 500) {
+ output = chars.slice(0, 497).join("") + "...";
+} else {
+ output = input;
+}
+
+fs.writeFileSync(
+ `android/fastlane/metadata/android/en-US/changelogs/${buildVersion}.txt`,
+ output,
+ "utf8"
+);
diff --git a/.github/workflows/build-develop.yml b/.github/workflows/build-develop.yml
index 48ffc62e564..5e9969daf74 100644
--- a/.github/workflows/build-develop.yml
+++ b/.github/workflows/build-develop.yml
@@ -8,17 +8,26 @@ on:
branches:
- 'develop'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
run-eslint-and-test:
name: ESLint and Test
if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }}
uses: ./.github/workflows/eslint.yml
+ generate-changelog:
+ name: Generate Release Changelog
+ needs: [run-eslint-and-test]
+ uses: ./.github/workflows/generate-changelog.yml
+
android-build-experimental-store:
name: Build Android Experimental
if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }}
uses: ./.github/workflows/build-android.yml
- needs: [run-eslint-and-test]
+ needs: [run-eslint-and-test, generate-changelog]
secrets: inherit
with:
type: experimental
@@ -28,7 +37,7 @@ jobs:
name: Build Android Official
if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }}
uses: ./.github/workflows/build-official-android.yml
- needs: [run-eslint-and-test]
+ needs: [run-eslint-and-test, generate-changelog]
secrets: inherit
with:
type: official
@@ -38,7 +47,7 @@ jobs:
name: Build iOS Experimental
if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }}
uses: ./.github/workflows/build-ios.yml
- needs: [run-eslint-and-test]
+ needs: [run-eslint-and-test, generate-changelog]
secrets: inherit
with:
type: experimental
@@ -48,7 +57,7 @@ jobs:
name: Build iOS Official
if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }}
uses: ./.github/workflows/build-official-ios.yml
- needs: [run-eslint-and-test]
+ needs: [run-eslint-and-test, generate-changelog]
secrets: inherit
with:
type: official
diff --git a/.github/workflows/build-official-android.yml b/.github/workflows/build-official-android.yml
index 76d195085b0..226c50ff26a 100644
--- a/.github/workflows/build-official-android.yml
+++ b/.github/workflows/build-official-android.yml
@@ -72,7 +72,7 @@ jobs:
upload-android:
name: Upload
runs-on: ubuntu-latest
- needs: [upload-hold]
+ needs: [build-android, upload-hold]
if: ${{ inputs.type == 'official' && (always() && (needs.upload-hold.result == 'success' || needs.upload-hold.result == 'skipped')) }}
steps:
- name: Checkout Repository
@@ -85,7 +85,7 @@ jobs:
trigger: ${{ inputs.trigger }}
FASTLANE_GOOGLE_SERVICE_ACCOUNT: ${{ secrets.FASTLANE_GOOGLE_SERVICE_ACCOUNT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- BUILD_VERSION: ${{ needs.upload-hold.outputs.BUILD_VERSION }}
+ BUILD_VERSION: ${{ needs.build-android.outputs.BUILD_VERSION }}
upload-internal:
name: Internal Sharing
diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml
new file mode 100644
index 00000000000..af729951373
--- /dev/null
+++ b/.github/workflows/generate-changelog.yml
@@ -0,0 +1,38 @@
+name: Generate Release Changelog
+
+on:
+ workflow_call:
+
+jobs:
+ generate-changelog:
+ name: Generate changelog
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Generate changelog
+ shell: bash
+ run: |
+ LATEST_RELEASE_TAG=$(git tag --sort=-creatordate | head -n 1)
+
+ if [ -z "$LATEST_RELEASE_TAG" ]; then
+ echo "- Improvements and bug fixes" > changelog.txt
+ exit 0
+ fi
+
+ git log "$LATEST_RELEASE_TAG"..HEAD --pretty=format:"- %s" --no-merges > changelog.txt
+
+ if [ ! -s changelog.txt ]; then
+ echo "- Improvements and bug fixes" > changelog.txt
+ fi
+
+ - name: Upload changelog artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: release-changelog
+ path: changelog.txt
+ retention-days: 15
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4abd5930749..f0f06675f4c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -90,7 +90,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
- versionName "4.68.2"
+ versionName "4.69.0"
vectorDrawables.useSupportLibrary = true
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
resValue "string", "rn_config_reader_custom_package", "chat.rocket.reactnative"
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 0a96cf5b1c1..22339abd691 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
@@ -18,9 +18,6 @@
import androidx.annotation.Nullable;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
-import com.bumptech.glide.request.RequestOptions;
import com.google.gson.Gson;
import java.util.ArrayList;
@@ -29,9 +26,6 @@
import java.util.List;
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;
@@ -74,7 +68,7 @@ public CustomPushNotification(Context context, Bundle bundle) {
public static void clearMessages(int notId) {
notificationMessages.remove(Integer.toString(notId));
}
-
+
public void onReceived() {
String notId = mBundle.getString("notId");
@@ -90,6 +84,8 @@ public void onReceived() {
return;
}
+ // Process notification immediately - no need to wait for React Native
+ // MMKV is initialized at app startup, so all notification types can work without React
try {
handleNotification();
} catch (Exception e) {
@@ -99,7 +95,7 @@ public void onReceived() {
private void handleNotification() {
Ejson receivedEjson = safeFromJson(mBundle.getString("ejson", "{}"), Ejson.class);
-
+
if (receivedEjson != null && receivedEjson.notificationType != null && receivedEjson.notificationType.equals("message-id-only")) {
Log.d(TAG, "Detected message-id-only notification, will fetch full content from server");
loadNotificationAndProcess(receivedEjson);
@@ -136,27 +132,26 @@ public void call(@Nullable Bundle bundle) {
}
private void processNotification() {
- final Bundle bundle = mBundle;
- 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) {
Log.d(TAG, "[processNotification] notId=" + notId);
- Log.d(TAG, "[processNotification] bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false));
- Log.d(TAG, "[processNotification] bundle.title=" + (bundle.getString("title") != null ? "[present]" : "[null]"));
- Log.d(TAG, "[processNotification] bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0));
+ 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);
- return;
+ handleE2ENotification(mBundle, loadedEjson, notId);
+ return; // handleE2ENotification will decrypt and show the notification
}
- // Handle regular notifications
- showNotification(bundle, loadedEjson, notId);
+ // Handle regular (non-E2E) notifications
+ showNotification(mBundle, loadedEjson, notId);
}
/**
@@ -167,19 +162,12 @@ private boolean isE2ENotification(Ejson ejson) {
}
/**
- * Handles E2E encrypted notifications
+ * Handles E2E encrypted notifications by decrypting immediately using regular Android Context.
+ * No longer waits for React Native initialization.
*/
private void handleE2ENotification(Bundle bundle, Ejson ejson, String notId) {
- if (Encryption.shared == null) {
- Log.e(TAG, "Encryption singleton is null, cannot decrypt E2E notification");
- bundle.putString("message", "Encrypted message");
- synchronized(this) {
- mBundle = bundle;
- }
- showNotification(bundle, ejson, notId);
- return;
- }
-
+ // Decrypt immediately using regular Android Context (mContext)
+ // This works without React Native initialization
String decrypted = Encryption.shared.decryptMessage(ejson, mContext);
if (decrypted != null) {
@@ -191,6 +179,7 @@ private void handleE2ENotification(Bundle bundle, Ejson ejson, String notId) {
} else {
Log.w(TAG, "E2E decryption failed for notification, showing fallback notification");
// Show fallback notification so user knows a message arrived
+ // Use a placeholder message since we can't decrypt
bundle.putString("message", "Encrypted message");
synchronized(this) {
mBundle = bundle;
@@ -213,16 +202,19 @@ private void showNotification(Bundle bundle, Ejson ejson, String notId) {
boolean hasSender = ejson != null && ejson.sender != null;
String title = bundle.getString("title");
+ String displaySenderName = (ejson != null && ejson.senderName != null && !ejson.senderName.isEmpty())
+ ? ejson.senderName
+ : (hasSender ? ejson.sender.username : title);
+
bundle.putLong("time", new Date().getTime());
- bundle.putString("username", hasSender ? ejson.sender.username : title);
+ bundle.putString("username", displaySenderName);
bundle.putString("senderId", hasSender ? ejson.sender._id : "1");
String avatarUri = ejson != null ? ejson.getAvatarUri() : null;
- if (ENABLE_VERBOSE_LOGS) {
- Log.d(TAG, "[showNotification] avatarUri=" + (avatarUri != null ? "[present]" : "[null]"));
- }
bundle.putString("avatarUri", avatarUri);
+ // Ensure mBundle is updated with all modifications before building notification
+ // This ensures buildNotification() sees the complete bundle with all fields (including ejson)
synchronized(this) {
mBundle = bundle;
}
@@ -295,24 +287,27 @@ private void createNotificationChannel() {
}
private Notification.Builder buildNotification(int notificationId) {
- final Bundle bundle = mBundle;
String notId = Integer.toString(notificationId);
- String title = bundle.getString("title");
- String message = bundle.getString("message");
- Boolean notificationLoaded = bundle.getBoolean("notificationLoaded", false);
- Ejson ejson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class);
+ String title = mBundle.getString("title");
+ String message = mBundle.getString("message");
+ Boolean notificationLoaded = mBundle.getBoolean("notificationLoaded", false);
+ Ejson ejson = safeFromJson(mBundle.getString("ejson", "{}"), Ejson.class);
+
+ // Determine the correct title based on notification type
+ String notificationTitle = title;
if (ENABLE_VERBOSE_LOGS) {
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] notificationTitle=" + (notificationTitle != 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(bundle);
+ intent.putExtras(mBundle);
PendingIntent pendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -329,7 +324,7 @@ private Notification.Builder buildNotification(int notificationId) {
}
notification
- .setContentTitle(title)
+ .setContentTitle(notificationTitle)
.setContentText(message)
.setContentIntent(pendingIntent)
.setPriority(Notification.PRIORITY_HIGH)
@@ -337,14 +332,14 @@ private Notification.Builder buildNotification(int notificationId) {
.setAutoCancel(true);
notificationColor(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) {
Log.i(TAG, "[buildNotification] ✅ Rendering FULL notification style");
- notificationStyle(notification, notificationId, bundle);
- notificationReply(notification, notificationId, bundle);
+ notificationStyle(notification, notificationId, mBundle);
+ notificationReply(notification, notificationId, mBundle);
} else {
Log.w(TAG, "[buildNotification] ⚠️ Rendering FALLBACK notification");
// Cancel previous fallback notifications from same server
@@ -378,37 +373,7 @@ private void cancelPreviousFallbackNotifications(Ejson ejson) {
}
private Bitmap getAvatar(String uri) {
- if (uri == null || uri.isEmpty()) {
- if (ENABLE_VERBOSE_LOGS) {
- Log.w(TAG, "getAvatar called with null/empty URI");
- }
- return largeIcon();
- }
-
- if (ENABLE_VERBOSE_LOGS) {
- 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(3, TimeUnit.SECONDS);
-
- return avatar != null ? avatar : largeIcon();
- } catch (final ExecutionException | InterruptedException | TimeoutException e) {
- Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e);
- return largeIcon();
- }
+ return NotificationHelper.fetchAvatarBitmap(mContext, uri, largeIcon());
}
private Bitmap largeIcon() {
@@ -429,7 +394,10 @@ private void notificationIcons(Notification.Builder notification, Bundle bundle)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
String avatarUri = ejson != null ? ejson.getAvatarUri() : null;
if (avatarUri != null) {
- notification.setLargeIcon(getAvatar(avatarUri));
+ Bitmap avatar = getAvatar(avatarUri);
+ if (avatar != null) {
+ notification.setLargeIcon(avatar);
+ }
}
}
}
@@ -440,8 +408,11 @@ 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);
+ if (pos == -1) {
+ return message;
+ }
+ int start = pos + 2;
+ return start <= message.length() ? message.substring(start) : "";
}
return message;
}
@@ -482,7 +453,10 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun
}
String title = bundle.getString("title");
- messageStyle.setConversationTitle(title);
+ // Determine the correct conversation title based on notification type
+ Ejson bundleEjson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class);
+ String conversationTitle = title;
+ messageStyle.setConversationTitle(conversationTitle);
if (bundles != null) {
for (Bundle data : bundles) {
@@ -493,15 +467,17 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun
Ejson ejson = safeFromJson(data.getString("ejson", "{}"), Ejson.class);
String m = extractMessage(message, ejson);
+ String displaySenderName = (ejson != null && ejson.senderName != null && !ejson.senderName.isEmpty())
+ ? ejson.senderName
+ : (ejson != null && ejson.sender != null ? ejson.sender.username : title);
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
- String senderName = ejson != null ? ejson.senderName : "Unknown";
- messageStyle.addMessage(m, timestamp, senderName);
+ messageStyle.addMessage(m, timestamp, displaySenderName);
} else {
Bitmap avatar = getAvatar(avatarUri);
- String senderName = ejson != null ? ejson.senderName : "Unknown";
Person.Builder senderBuilder = new Person.Builder()
.setKey(senderId)
- .setName(senderName);
+ .setName(displaySenderName);
if (avatar != null) {
senderBuilder.setIcon(Icon.createWithBitmap(avatar));
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 ffb403e342f..e9998ff8231 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
@@ -5,6 +5,8 @@
import com.tencent.mmkv.MMKV;
import java.math.BigInteger;
+import java.net.URLEncoder;
+import java.io.UnsupportedEncodingException;
import chat.rocket.reactnative.BuildConfig;
import chat.rocket.reactnative.storage.MMKVKeyManager;
@@ -33,6 +35,7 @@ public class Ejson {
String notificationType;
String messageType;
String senderName;
+ String name; // Room name for groups/channels
String msg;
Integer status; // For video conf: 0=incoming, 4=cancelled
@@ -50,33 +53,76 @@ private MMKV getMMKV() {
return MMKV.mmkvWithID("default", MMKV.SINGLE_PROCESS_MODE);
}
- public String getAvatarUri() {
- if (sender == null || sender.username == null || sender.username.isEmpty()) {
- Log.w(TAG, "Cannot generate avatar URI: sender or username is null");
- return null;
- }
-
+ /**
+ * Helper method to build avatar URI from avatar path.
+ * Validates server URL and credentials, then constructs the full URI.
+ */
+ private String buildAvatarUri(String avatarPath, String errorContext) {
String server = serverURL();
if (server == null || server.isEmpty()) {
- Log.w(TAG, "Cannot generate avatar URI: serverURL is null");
+ Log.w(TAG, "Cannot generate " + errorContext + " avatar URI: serverURL is null");
return null;
}
String userToken = token();
String uid = userId();
- if (userToken.isEmpty() || uid.isEmpty()) {
- Log.w(TAG, "Cannot generate avatar URI: missing auth credentials (token=" + !userToken.isEmpty() + ", uid=" + !uid.isEmpty() + ")");
- return null;
+ String finalUri = server + avatarPath + "?format=png&size=100";
+ if (!userToken.isEmpty() && !uid.isEmpty()) {
+ finalUri += "&rc_token=" + userToken + "&rc_uid=" + uid;
}
- String uri = server + "/avatar/" + sender.username + "?format=png&size=100&rc_token=" + userToken + "&rc_uid=" + uid;
+ return finalUri;
+ }
+
+ public String getAvatarUri() {
+ String avatarPath;
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "Generated avatar URI for user: " + sender.username);
+ if ("d".equals(type)) {
+ if (sender == null || sender.username == null || sender.username.isEmpty()) {
+ Log.w(TAG, "Cannot generate avatar URI: sender or username is null");
+ return null;
+ }
+ try {
+ avatarPath = "/avatar/" + URLEncoder.encode(sender.username, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Failed to encode username", e);
+ return null;
+ }
+ } else {
+ if (rid == null || rid.isEmpty()) {
+ Log.w(TAG, "Cannot generate avatar URI: rid is null for non-DM");
+ return null;
+ }
+ try {
+ avatarPath = "/avatar/room/" + URLEncoder.encode(rid, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Failed to encode rid", e);
+ return null;
+ }
}
- return uri;
+ return buildAvatarUri(avatarPath, "");
+ }
+
+ /**
+ * Generates avatar URI for video conference caller.
+ * Returns null if caller username is not available (username is required for avatar endpoint).
+ */
+ public String getCallerAvatarUri() {
+ // Check if caller exists and has username (required - /avatar/{userId} endpoint doesn't exist)
+ if (caller == null || caller.username == null || caller.username.isEmpty()) {
+ Log.w(TAG, "Cannot generate caller avatar URI: caller or username is null");
+ return null;
+ }
+
+ try {
+ String avatarPath = "/avatar/" + URLEncoder.encode(caller.username, "UTF-8");
+ return buildAvatarUri(avatarPath, "caller");
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Failed to encode caller username", e);
+ return null;
+ }
}
public String token() {
@@ -187,6 +233,7 @@ static class Sender {
static class Caller {
String _id;
String name;
+ String username;
}
static class Content {
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 3b53a0be419..db21a2fcb46 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,6 +1,18 @@
package chat.rocket.reactnative.notification;
-import android.os.Build;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.RequestOptions;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import chat.rocket.reactnative.BuildConfig;
@@ -32,10 +44,44 @@ public static String sanitizeUrl(String url) {
* @return User-Agent string
*/
public static String getUserAgent() {
- String systemVersion = Build.VERSION.RELEASE;
+ String systemVersion = android.os.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);
}
+
+ /**
+ * Fetches avatar bitmap from URI using Glide.
+ * Uses a 3-second timeout to avoid blocking the FCM service for too long.
+ *
+ * @param context The application context
+ * @param uri The avatar URI to fetch
+ * @param fallbackIcon Optional fallback bitmap (null if no fallback desired)
+ * @return Avatar bitmap, or fallbackIcon if fetch fails, or null if no fallback provided
+ */
+ public static Bitmap fetchAvatarBitmap(Context context, String uri, @Nullable Bitmap fallbackIcon) {
+ if (uri == null || uri.isEmpty()) {
+ return fallbackIcon;
+ }
+
+ 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(context)
+ .asBitmap()
+ .apply(RequestOptions.bitmapTransform(new RoundedCorners(10)))
+ .load(uri)
+ .submit(100, 100)
+ .get(3, TimeUnit.SECONDS);
+
+ return avatar != null ? avatar : fallbackIcon;
+ } catch (final ExecutionException | InterruptedException | TimeoutException e) {
+ Log.e("NotificationHelper", "Failed to fetch avatar", e);
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ return fallbackIcon;
+ }
+ }
}
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
index b783e33e1b0..92846f46ac8 100644
--- a/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt
+++ b/android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt
@@ -8,6 +8,7 @@ import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.RingtoneManager
+import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
import android.util.Log
@@ -90,11 +91,11 @@ class VideoConfNotification(private val context: Context) {
if (ejson.caller != null) {
callerId = ejson.caller._id ?: ""
- callerName = ejson.caller.name ?: "Unknown"
+ callerName = ejson.senderName ?: 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"
+ callerName = ejson.senderName ?: ejson.sender.name ?: "Unknown"
} else {
callerId = ""
callerName = "Unknown"
@@ -155,6 +156,14 @@ class VideoConfNotification(private val context: Context) {
val packageName = context.packageName
val smallIconResId = context.resources.getIdentifier("ic_notification", "drawable", packageName)
+ // Fetch caller avatar
+ val avatarUri = ejson.getCallerAvatarUri()
+ val avatarBitmap = if (avatarUri != null) {
+ getAvatar(avatarUri)
+ } else {
+ null
+ }
+
// Build notification
val builder = NotificationCompat.Builder(context, CHANNEL_ID).apply {
setSmallIcon(smallIconResId)
@@ -169,6 +178,11 @@ class VideoConfNotification(private val context: Context) {
setContentIntent(fullScreenPendingIntent)
addAction(0, "Decline", declinePendingIntent)
addAction(0, "Accept", acceptPendingIntent)
+
+ // Set large icon (avatar) if available
+ if (avatarBitmap != null) {
+ setLargeIcon(avatarBitmap)
+ }
}
// Set sound for pre-O devices
@@ -194,6 +208,14 @@ class VideoConfNotification(private val context: Context) {
return PendingIntent.getActivity(context, requestCode, intent, flags)
}
+ /**
+ * Fetches avatar bitmap from URI using Glide.
+ * Returns null if fetch fails or times out, in which case notification will display without avatar.
+ */
+ private fun getAvatar(uri: String): Bitmap? {
+ return NotificationHelper.fetchAvatarBitmap(context, uri, null)
+ }
+
/**
* Cancels a video call notification.
*
diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile
index b89435389ce..1448a1173f0 100644
--- a/android/fastlane/Fastfile
+++ b/android/fastlane/Fastfile
@@ -78,7 +78,11 @@ platform :android do
upload_to_play_store(
package_name: 'chat.rocket.android',
track: 'beta',
- aab: 'app/build/outputs/bundle/officialRelease/app-official-release.aab'
+ aab: 'app/build/outputs/bundle/officialRelease/app-official-release.aab',
+ skip_upload_metadata: true,
+ skip_upload_changelogs: false,
+ skip_upload_images: true,
+ skip_upload_screenshots: true
)
end
end
diff --git a/app/actions/room.ts b/app/actions/room.ts
index 1ac3b0f4907..a4811411344 100644
--- a/app/actions/room.ts
+++ b/app/actions/room.ts
@@ -39,9 +39,14 @@ interface IForwardRoom extends Action {
rid: string;
}
+type IUserTypingArgs = {
+ tmid?: string;
+};
+
interface IUserTyping extends Action {
rid: string;
status: boolean;
+ args?: IUserTypingArgs;
}
export interface IRoomHistoryRequest extends Action {
@@ -109,11 +114,12 @@ export function removedRoom(): Action {
};
}
-export function userTyping(rid: string, status = true): IUserTyping {
+export function userTyping(rid: string, status = true, args?: IUserTypingArgs): IUserTyping {
return {
type: ROOM.USER_TYPING,
rid,
- status
+ status,
+ args
};
}
diff --git a/app/containers/Avatar/Avatar.stories.tsx b/app/containers/Avatar/Avatar.stories.tsx
index ed07c9d408c..0a2503e3c7d 100644
--- a/app/containers/Avatar/Avatar.stories.tsx
+++ b/app/containers/Avatar/Avatar.stories.tsx
@@ -12,6 +12,8 @@ const styles = StyleSheet.create({
});
const server = 'https://open.rocket.chat';
+const base64Image =
+ 'data:image/png;base64,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';
export default {
title: 'Avatar'
@@ -31,6 +33,8 @@ export const AvatarUrl = () => (
export const AvatarPath = () => ;
+export const AvatarBase64 = () => ;
+
export const WithETag = () => (
);
diff --git a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap
index 97d9de5ea35..0a0d7df4ae6 100644
--- a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap
+++ b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap
@@ -1,5 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Story Snapshots: AvatarBase64 should match snapshot 1`] = `
+
+
+
+`;
+
exports[`Story Snapshots: AvatarExternalProviderUrl should match snapshot 1`] = `
{
if (sharing || !rid) return;
- dispatch(userTyping(rid, isTyping));
+ dispatch(userTyping(rid, isTyping, tmid ? { tmid } : {}));
};
return (
diff --git a/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx b/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx
index 1cf9415a3d5..dd9bdfa8672 100644
--- a/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx
+++ b/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx
@@ -22,7 +22,7 @@ export const ReviewButton = ({ onPress }: { onPress: Function }): ReactElement =
onPress={() => onPress()}
hitSlop={hitSlop}>
-
+
);
diff --git a/app/containers/UIKit/Actions.tsx b/app/containers/UIKit/Actions.tsx
index 15f01af1c64..a103ca2bbe5 100644
--- a/app/containers/UIKit/Actions.tsx
+++ b/app/containers/UIKit/Actions.tsx
@@ -1,22 +1,44 @@
import React, { useState } from 'react';
+import { View, StyleSheet } from 'react-native';
import { BlockContext } from '@rocket.chat/ui-kit';
import Button from '../Button';
import I18n from '../../i18n';
import { type IActions } from './interfaces';
+const styles = StyleSheet.create({
+ hidden: {
+ overflow: 'hidden',
+ height: 0
+ }
+});
+
export const Actions = ({ blockId, appId, elements, parser }: IActions) => {
const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5);
- const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements;
+ const shouldShowMore = elements && elements.length > 5;
+ const maxVisible = 5;
+
+ if (!elements || !parser) {
+ return null;
+ }
+
+ // Always render all elements to maintain consistent hook calls
+ // This ensures hooks are always called in the same order
+ // Use View wrapper to conditionally hide elements instead of conditionally rendering
return (
<>
- <>
- {renderedElements
- ? renderedElements?.map(element => parser?.renderActions({ blockId, appId, ...element }, BlockContext.ACTION, parser))
- : null}
- >
- {showMoreVisible && ,
-
- Deny
-
+
+ Deny
+
+
,
-
+[
+ ,
+ ,
+ ,
+
-
-
-
+
+
-
-
+ undefined,
+ ]
+ }
+ testID="icon_container"
+ >
+
+
+
+
+
-
-
+ ,
+]
`;
exports[`Story Snapshots: Fields should match snapshot 1`] = `
diff --git a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap
index f56a0b1db77..a7006ed5b4f 100644
--- a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap
+++ b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap
@@ -801,6 +801,968 @@ exports[`Story Snapshots: ModalActions should match snapshot 1`] = `
]
`;
+exports[`Story Snapshots: ModalActionsWithShowMore should match snapshot 1`] = `
+[
+
+
+
+
+
+
+
+ Actions with Show More
+
+
+
+
+
+
+ 🚀
+
+
+
+
+
+
+
+ This modal demonstrates the "Show more" functionality. The actions block has 8 buttons, but only the first 5 are visible initially. Click "Show more" to reveal all buttons.
+
+
+
+
+
+ ,
+ ,
+
+
+
+ Primary Action
+
+
+ ,
+
+
+
+ Secondary
+
+
+ ,
+
+
+
+ Danger Action
+
+
+ ,
+
+
+
+ Button 4
+
+
+ ,
+
+
+
+ Button 5
+
+
+ ,
+
+
+
+ Button 6 - Hidden
+
+
+ ,
+
+
+
+ Button 7 - Hidden
+
+
+ ,
+
+
+
+ Button 8 - Hidden
+
+
+ ,
+
+
+ Show more
+
+ ,
+
+
+
+
+
+
+ This actions block has
+
+
+
+ 8 buttons
+
+
+
+ but only shows
+
+
+
+ 5
+
+
+
+ initially. Click "Show more" to see all buttons!
+
+
+
+
+
+ ,
+]
+`;
+
exports[`Story Snapshots: ModalContextsDividers should match snapshot 1`] = `
[
{
const { colors } = useTheme();
- const formatDate = React.useMemo(() => {
- const timestamp = parseInt(value.timestamp) * 1000;
+ const timestampMs = React.useMemo(() => parseInt(value.timestamp, 10) * 1000, [value.timestamp]);
+ const formatDate = React.useMemo(() => {
if (value.format === 't') {
- return dayjs(timestamp).format('hh:mm A');
+ return dayjs(timestampMs).format('hh:mm A');
}
if (value.format === 'T') {
- return dayjs(timestamp).format('hh:mm:ss A');
+ return dayjs(timestampMs).format('hh:mm:ss A');
}
if (value.format === 'd') {
- return dayjs(timestamp).format('MM/DD/YYYY');
+ return dayjs(timestampMs).format('MM/DD/YYYY');
}
if (value.format === 'D') {
- return dayjs(timestamp).format('dddd, MMM DD, YYYY');
+ return dayjs(timestampMs).format('dddd, MMM DD, YYYY');
}
if (value.format === 'f') {
- return dayjs(timestamp).format('dddd, MMM DD, YYYY hh:mm A');
+ return dayjs(timestampMs).format('dddd, MMM DD, YYYY hh:mm A');
}
if (value.format === 'F') {
- return dayjs(timestamp).format('dddd, MMM DD, YYYY hh:mm:ss A');
+ return dayjs(timestampMs).format('dddd, MMM DD, YYYY hh:mm:ss A');
}
if (value.format === 'R') {
- return dayjs(timestamp).fromNow();
+ return dayjs(timestampMs).fromNow();
}
return 'Invalid Date';
- }, [value]);
+ }, [timestampMs, value.format]);
const handlePress = React.useCallback(() => {
- const message = dayjs(parseInt(value.timestamp) * 1000).format('dddd, MMM DD, YYYY hh:mm A');
+ const message = dayjs(timestampMs).format('dddd, MMM DD, YYYY hh:mm A');
EventEmitter.emit(LISTENER, { message });
- }, [value.timestamp]);
+ }, [timestampMs]);
return (
({ PixelRatio: { get: () => 1 } }));
+jest.mock('./compareServerVersion', () => ({
+ compareServerVersion: jest.fn()
+}));
+
+const mockCompareServerVersion = compareServerVersion as jest.MockedFunction;
describe('formatUrl function', () => {
test('formats the default URL to get the user avatar', () => {
@@ -30,3 +37,176 @@ describe('formatUrl function', () => {
expect(result).toEqual(expected);
});
});
+
+describe('getAvatarURL function', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('returns the avatar unchanged when it is a base64 data URI', () => {
+ const avatar = 'data:image/png;base64,ABC123';
+
+ const expected = avatar;
+ const result = getAvatarURL({ avatar });
+ expect(result).toEqual(expected);
+ });
+
+ test('returns the avatar unchanged when it starts with http', () => {
+ const avatar = 'https://example.com/avatar.png';
+ const server = 'https://mobile.qa.rocket.chat';
+
+ const expected = avatar;
+ const result = getAvatarURL({ avatar, server });
+ expect(result).toEqual(expected);
+ });
+
+ test('formats avatar URL with server when avatar does not start with http', () => {
+ const avatar = '/avatar/user123';
+ const server = 'https://mobile.qa.rocket.chat';
+ const size = 30;
+
+ const expected = 'https://mobile.qa.rocket.chat/avatar/user123?format=png&size=30';
+ const result = getAvatarURL({ avatar, server, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('uses external provider URL for direct messages', () => {
+ const type = SubscriptionType.DIRECT;
+ const text = 'username123';
+ const avatarExternalProviderUrl = 'https://external.provider.com/avatar/{username}';
+ const size = 30;
+
+ const expected = 'https://external.provider.com/avatar/username123?format=png&size=30';
+ const result = getAvatarURL({ type, text, avatarExternalProviderUrl, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('uses room avatar external provider URL when serverVersion >= 3.8.0', () => {
+ const rid = 'room123';
+ const serverVersion = '3.8.0';
+ const roomAvatarExternalProviderUrl = 'https://external.provider.com/room/{roomId}';
+ const size = 30;
+
+ mockCompareServerVersion.mockReturnValue(true);
+
+ const expected = 'https://external.provider.com/room/room123?format=png&size=30';
+ const result = getAvatarURL({ rid, serverVersion, roomAvatarExternalProviderUrl, size });
+ expect(result).toEqual(expected);
+ expect(mockCompareServerVersion).toHaveBeenCalledWith('3.8.0', 'greaterThanOrEqualTo', '3.8.0');
+ });
+
+ test('uses room/{rid} format when serverVersion >= 3.6.0', () => {
+ const rid = 'room123';
+ const serverVersion = '3.6.0';
+ const server = 'https://mobile.qa.rocket.chat';
+ const size = 30;
+ const text = 'roomname';
+
+ // compareServerVersion returns false for 'lowerThan' when version >= 3.6.0
+ // The condition is !compareServerVersion(..., 'lowerThan', '3.6.0')
+ // So we need to return false to make !false = true
+ mockCompareServerVersion.mockReturnValue(false);
+
+ const expected = 'https://mobile.qa.rocket.chat/avatar/room/room123?format=png&size=30';
+ const result = getAvatarURL({ rid, serverVersion, server, size, text });
+ expect(result).toEqual(expected);
+ expect(mockCompareServerVersion).toHaveBeenCalledWith('3.6.0', 'lowerThan', '3.6.0');
+ });
+
+ test('uses @{text} format when serverVersion < 3.6.0 or no rid', () => {
+ const text = 'username123';
+ const serverVersion = '3.5.0';
+ const server = 'https://mobile.qa.rocket.chat';
+ const size = 30;
+
+ mockCompareServerVersion.mockReturnValue(false);
+
+ const expected = 'https://mobile.qa.rocket.chat/avatar/@username123?format=png&size=30';
+ const result = getAvatarURL({ text, serverVersion, server, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('adds authentication query parameters when userId, token, and blockUnauthenticatedAccess are provided', () => {
+ const avatar = '/avatar/user123';
+ const server = 'https://mobile.qa.rocket.chat';
+ const userId = 'user123';
+ const token = 'token123';
+ const blockUnauthenticatedAccess = true;
+ const size = 30;
+
+ const expected = 'https://mobile.qa.rocket.chat/avatar/user123?format=png&size=30&rc_token=token123&rc_uid=user123';
+ const result = getAvatarURL({ avatar, server, userId, token, blockUnauthenticatedAccess, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('adds avatarETag query parameter when provided', () => {
+ const avatar = '/avatar/user123';
+ const server = 'https://mobile.qa.rocket.chat';
+ const avatarETag = 'etag123';
+ const size = 30;
+
+ const expected = 'https://mobile.qa.rocket.chat/avatar/user123?format=png&size=30&etag=etag123';
+ const result = getAvatarURL({ avatar, server, avatarETag, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('adds both authentication and etag query parameters when all are provided', () => {
+ const avatar = '/avatar/user123';
+ const server = 'https://mobile.qa.rocket.chat';
+ const userId = 'user123';
+ const token = 'token123';
+ const blockUnauthenticatedAccess = true;
+ const avatarETag = 'etag123';
+ const size = 30;
+
+ const expected =
+ 'https://mobile.qa.rocket.chat/avatar/user123?format=png&size=30&rc_token=token123&rc_uid=user123&etag=etag123';
+ const result = getAvatarURL({ avatar, server, userId, token, blockUnauthenticatedAccess, avatarETag, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('uses cdnPrefix when provided and starts with http', () => {
+ const avatar = '/avatar/user123';
+ const server = 'https://mobile.qa.rocket.chat';
+ const cdnPrefix = 'https://cdn.example.com';
+ const size = 30;
+
+ const expected = 'https://cdn.example.com/avatar/user123?format=png&size=30';
+ const result = getAvatarURL({ avatar, server, cdnPrefix, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('returns default avatar URL when no avatar is provided', () => {
+ const text = 'username123';
+ const server = 'https://mobile.qa.rocket.chat';
+ const size = 30;
+
+ mockCompareServerVersion.mockReturnValue(false);
+
+ const expected = 'https://mobile.qa.rocket.chat/avatar/@username123?format=png&size=30';
+ const result = getAvatarURL({ text, server, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('trims trailing slashes from external provider URLs', () => {
+ const type = SubscriptionType.DIRECT;
+ const text = 'username123';
+ const avatarExternalProviderUrl = 'https://external.provider.com/avatar/{username}//';
+ const size = 30;
+
+ const expected = 'https://external.provider.com/avatar/username123?format=png&size=30';
+ const result = getAvatarURL({ type, text, avatarExternalProviderUrl, size });
+ expect(result).toEqual(expected);
+ });
+
+ test('trims trailing slashes from cdnPrefix', () => {
+ const avatar = '/avatar/user123';
+ const server = 'https://mobile.qa.rocket.chat';
+ const cdnPrefix = 'https://cdn.example.com///';
+ const size = 30;
+
+ const expected = 'https://cdn.example.com/avatar/user123?format=png&size=30';
+ const result = getAvatarURL({ avatar, server, cdnPrefix, size });
+ expect(result).toEqual(expected);
+ });
+});
diff --git a/app/lib/methods/helpers/getAvatarUrl.ts b/app/lib/methods/helpers/getAvatarUrl.ts
index b59880472fa..d76f0ccdc65 100644
--- a/app/lib/methods/helpers/getAvatarUrl.ts
+++ b/app/lib/methods/helpers/getAvatarUrl.ts
@@ -25,6 +25,9 @@ export const getAvatarURL = ({
roomAvatarExternalProviderUrl,
cdnPrefix
}: IAvatar): string => {
+ if (!!avatar && avatar?.startsWith('data:')) {
+ return avatar;
+ }
let room;
if (type === SubscriptionType.DIRECT) {
room = text;
diff --git a/app/lib/methods/helpers/openLink.ts b/app/lib/methods/helpers/openLink.ts
index 8ded0d5c0ca..04492889f8e 100644
--- a/app/lib/methods/helpers/openLink.ts
+++ b/app/lib/methods/helpers/openLink.ts
@@ -23,15 +23,15 @@ const appSchemeURL = (url: string, browser: string): string => {
const { protocol } = parsedUrl;
const isSecure = ['https:'].includes(protocol);
- if (browser === 'googlechrome') {
+ if (browser === 'Chrome') {
if (!isSecure) {
schemeUrl = url.replace(protocol, scheme.chrome);
} else {
schemeUrl = url.replace(protocol, scheme.chromeSecure);
}
- } else if (browser === 'firefox') {
+ } else if (browser === 'Firefox') {
schemeUrl = `${scheme.firefox}//open-url?url=${url}`;
- } else if (browser === 'brave') {
+ } else if (browser === 'Brave') {
schemeUrl = `${scheme.brave}//open-url?url=${url}`;
}
@@ -52,7 +52,7 @@ const openLink = async (url: string, theme: TSupportedThemes = 'light'): Promise
url = ensureSecureProtocol(url);
try {
const browser = UserPreferences.getString(DEFAULT_BROWSER_KEY);
- if (browser === 'inApp') {
+ if (browser === 'In_app') {
await WebBrowser.openBrowserAsync(url, {
toolbarColor: themes[theme].surfaceNeutral,
controlsColor: themes[theme].fontSecondaryInfo,
diff --git a/app/lib/methods/loadMessagesForRoom.ts b/app/lib/methods/loadMessagesForRoom.ts
index 747b8b2aad2..10aa131ec5e 100644
--- a/app/lib/methods/loadMessagesForRoom.ts
+++ b/app/lib/methods/loadMessagesForRoom.ts
@@ -9,7 +9,6 @@ import updateMessages from './updateMessages';
import { generateLoadMoreId } from './helpers/generateLoadMoreId';
const COUNT = 50;
-const COUNT_LIMIT = COUNT * 10;
async function load({ rid: roomId, latest, t }: { rid: string; latest?: Date; t: RoomTypes }): Promise {
const apiType = roomTypeToApiType(t);
@@ -21,11 +20,11 @@ async function load({ rid: roomId, latest, t }: { rid: string; latest?: Date; t:
let mainMessagesCount = 0;
async function fetchBatch(lastTs?: string): Promise {
- if (allMessages.length >= COUNT_LIMIT) {
+ if (allMessages.length >= COUNT) {
return;
}
- const params = { roomId, count: COUNT, ...(lastTs && { latest: lastTs }) };
+ const params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) };
let data;
switch (apiType) {
@@ -77,7 +76,7 @@ export function loadMessagesForRoom(args: {
if (data?.length) {
const lastMessage = data[data.length - 1];
const lastMessageRecord = await getMessageById(lastMessage._id as string);
- if (!lastMessageRecord && (data.length === COUNT || data.length >= COUNT_LIMIT)) {
+ if (!lastMessageRecord && data.length === COUNT) {
const loadMoreMessage = {
_id: generateLoadMoreId(lastMessage._id as string),
rid: lastMessage.rid,
diff --git a/app/lib/notifications/index.ts b/app/lib/notifications/index.ts
index 15bff1ac5e9..47707cf964c 100644
--- a/app/lib/notifications/index.ts
+++ b/app/lib/notifications/index.ts
@@ -74,6 +74,8 @@ export const onNotification = (push: INotification): void => {
} catch (e) {
console.warn('[notifications/index.ts] Failed to parse ejson:', e);
}
+ } else {
+ console.warn('[notifications/index.ts] No ejson in payload, dispatching appInit');
}
store.dispatch(appInit());
};
diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts
index f38c096c315..95ad3f86c7e 100644
--- a/app/lib/services/restApi.ts
+++ b/app/lib/services/restApi.ts
@@ -950,14 +950,14 @@ export const addUsersToRoom = (rid: string): Promise => {
return sdk.methodCallWrapper('addUsersToRoom', { rid, users });
};
-export const emitTyping = (room: IRoom, typing = true) => {
+export const emitTyping = (room: IRoom, typing = true, args: { tmid?: string } = {}) => {
const { login, settings, server } = reduxStore.getState();
const { UI_Use_Real_Name } = settings;
const { version: serverVersion } = server;
const { user } = login;
const name = UI_Use_Real_Name ? user.name : user.username;
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.0.0')) {
- return sdk.methodCall('stream-notify-room', `${room}/user-activity`, name, typing ? ['user-typing'] : []);
+ return sdk.methodCall('stream-notify-room', `${room}/user-activity`, name, typing ? ['user-typing'] : [], args);
}
return sdk.methodCall('stream-notify-room', `${room}/typing`, name, typing);
};
diff --git a/app/sagas/room.js b/app/sagas/room.js
index 71bd73c717a..9fb160695f0 100644
--- a/app/sagas/room.js
+++ b/app/sagas/room.js
@@ -51,10 +51,10 @@ const clearInactiveTyping = function* clearInactiveTyping({ rid }) {
yield clearUserTyping({ rid, status: false });
};
-const watchUserTyping = function* watchUserTyping({ rid, status }) {
+const watchUserTyping = function* watchUserTyping({ rid, status, args }) {
try {
if (status) {
- yield emitTyping(rid, status);
+ yield emitTyping(rid, status, args);
if (inactiveTypingTask) {
yield cancel(inactiveTypingTask);
}
diff --git a/app/views/DefaultBrowserView/index.tsx b/app/views/DefaultBrowserView/index.tsx
index d16ccb399aa..5273ccbf10a 100644
--- a/app/views/DefaultBrowserView/index.tsx
+++ b/app/views/DefaultBrowserView/index.tsx
@@ -92,7 +92,7 @@ const DefaultBrowserView = () => {
isSelected={(!browser && item.value === 'systemDefault:') || item.title === browser}
title={item.title}
value={item.value}
- translateTitle={false}
+ translateTitle={['In_app', 'System_default'].includes(item.title)}
translateSubtitle={false}
onPress={changeDefaultBrowser}
testID={`default-browser-view-${item.value}`}
diff --git a/app/views/RoomsListView/components/Header.tsx b/app/views/RoomsListView/components/Header.tsx
index 6d56a9582f0..3f6ff738f90 100644
--- a/app/views/RoomsListView/components/Header.tsx
+++ b/app/views/RoomsListView/components/Header.tsx
@@ -36,6 +36,7 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string)
const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading);
const connected = useAppSelector(state => state.meteor.connected);
+ const isLoggingIn = useAppSelector(state => state.login.isFetching);
const isFetching = useAppSelector(state => state.rooms.isFetching);
const serverName = useAppSelector(state => state.settings.Site_Name as string);
const server = useAppSelector(state => state.server.server);
@@ -55,7 +56,7 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string)
let subtitle;
if (supportedVersionsStatus === 'expired') {
subtitle = 'Cannot connect';
- } else if (connecting) {
+ } else if (connecting || isLoggingIn) {
subtitle = I18n.t('Connecting');
} else if (isFetching) {
subtitle = I18n.t('Updating');
diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist
index 9d959502934..24b65a6e595 100644
--- a/ios/NotificationService/Info.plist
+++ b/ios/NotificationService/Info.plist
@@ -33,5 +33,16 @@
NSExtensionPrincipalClass
$(PRODUCT_MODULE_NAME).NotificationService
+ NSUserActivityTypes
+
+ INSendMessageIntent
+
+ NSExtensionAttributes
+
+ IntentsSupported
+
+ INSendMessageIntent
+
+
diff --git a/ios/NotificationService/NotificationService.entitlements b/ios/NotificationService/NotificationService.entitlements
index 4bfdb2c2b03..8d6bb8f66f9 100644
--- a/ios/NotificationService/NotificationService.entitlements
+++ b/ios/NotificationService/NotificationService.entitlements
@@ -2,6 +2,8 @@
+ com.apple.developer.usernotifications.communication
+
com.apple.security.application-groups
group.ios.chat.rocket
diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift
index d337a0061df..eb3f5b37e73 100644
--- a/ios/NotificationService/NotificationService.swift
+++ b/ios/NotificationService/NotificationService.swift
@@ -1,133 +1,330 @@
import UserNotifications
+import Intents
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
+ var finalContent: UNNotificationContent?
var rocketchat: RocketChat?
+ // MARK: - Notification Lifecycle
+
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
- 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)
- return
- }
+ if let bestAttemptContent = bestAttemptContent,
+ let ejsonString = bestAttemptContent.userInfo["ejson"] as? String,
+ let ejson = ejsonString.data(using: .utf8),
+ let payload = try? JSONDecoder().decode(Payload.self, from: ejson) {
+ rocketchat = RocketChat(server: payload.host.removeTrailingSlash())
- // Merge missing content notifications
- UNUserNotificationCenter.current().getDeliveredNotifications { deliveredNotifications in
- let identifiersToRemove = deliveredNotifications.filter {
- $0.request.content.body == "You have a new message"
- }.map { $0.request.identifier }
-
- if identifiersToRemove.count > 0 {
- UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiersToRemove)
- }
-
- // Request the content from server
- 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)
- }
- }
+ if payload.notificationType == .videoconf {
+ processVideoConf(payload: payload)
+ } else if payload.notificationType == .messageIdOnly {
+ fetchMessageContent(payload: payload)
+ } else {
+ processPayload(payload: payload)
}
+ } else {
+ contentHandler(request.content)
}
}
- func processVideoConf(payload: Payload, request: UNNotificationRequest) {
- guard let bestAttemptContent = bestAttemptContent else {
- return
- }
+ // MARK: - Processors
+
+ func processVideoConf(payload: Payload) {
+ guard let bestAttemptContent = bestAttemptContent else { return }
- // Status 4 means call cancelled/ended - remove any existing notification
+ // Handle Cancelled Calls
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"
-
+ // 1. Setup Basic Content
+ let callerName = payload.senderName ?? 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
- }
+ bestAttemptContent.interruptionLevel = .timeSensitive
- contentHandler?(bestAttemptContent)
+ // 2. Fetch Avatar & Activate Intent
+ fetchCallerAvatarData(from: payload) { [weak self] avatarData in
+ guard let self = self else { return }
+
+ self.activateCommunicationIntent(
+ senderName: callerName,
+ senderUsername: payload.caller?.username ?? "",
+ avatarData: avatarData,
+ conversationId: payload.rid ?? "",
+ isGroup: false,
+ groupName: nil
+ )
+
+ self.contentHandler?(self.finalContent ?? bestAttemptContent)
+ }
}
func processPayload(payload: Payload) {
- // If is a encrypted message
- if payload.messageType == .e2e {
- if let rid = payload.rid {
- let decryptedMessage: String?
-
- if let content = payload.content, (content.algorithm == "rc.v1.aes-sha2" || content.algorithm == "rc.v2.aes-sha2") {
- decryptedMessage = rocketchat?.decryptContent(rid: rid, content: content)
- } else if let msg = payload.msg, !msg.isEmpty {
- // Fallback to msg field
- decryptedMessage = rocketchat?.decryptContent(rid: rid, content: EncryptedContent(algorithm: "rc.v1.aes-sha2", ciphertext: msg, kid: nil, iv: nil))
- } else {
- decryptedMessage = nil
+ guard let bestAttemptContent = bestAttemptContent else { return }
+
+ // 1. Setup Basic Content (Title/Body)
+ let senderName = payload.senderName ?? payload.sender?.name ?? "Unknown"
+ let senderUsername = payload.sender?.username ?? payload.senderName ?? ""
+
+ if bestAttemptContent.title.isEmpty {
+ bestAttemptContent.title = senderName
+ }
+
+ if let roomType = payload.type {
+ if roomType == .group || roomType == .channel {
+ // Strip sender prefix if present
+ if let body = bestAttemptContent.body as? String {
+ let prefix = "\(senderUsername): "
+ if body.hasPrefix(prefix) {
+ bestAttemptContent.body = String(body.dropFirst(prefix.count))
+ } else {
+ // Try with sender name (display name) as fallback
+ let senderNamePrefix = "\(senderName): "
+ if body.hasPrefix(senderNamePrefix) {
+ bestAttemptContent.body = String(body.dropFirst(senderNamePrefix.count))
+ }
+ }
}
-
- if let decryptedMessage = decryptedMessage {
- bestAttemptContent?.body = decryptedMessage
- if let roomType = payload.type, roomType == .group, let sender = payload.senderName {
- bestAttemptContent?.body = "\(sender): \(decryptedMessage)"
+ }
+ }
+
+ // Handle Decryption (E2E)
+ if payload.messageType == .e2e, let rid = payload.rid {
+ if let decrypted = decryptMessage(payload: payload, rid: rid) {
+ bestAttemptContent.body = decrypted
+ }
+ }
+
+ // 2. Fetch Avatar & Activate Intent
+ fetchAvatarData(from: payload) { [weak self] avatarData in
+ guard let self = self else { return }
+
+ let isGroup = (payload.type == .group || payload.type == .channel)
+
+ self.activateCommunicationIntent(
+ senderName: senderName,
+ senderUsername: senderUsername,
+ avatarData: avatarData,
+ conversationId: payload.rid ?? "",
+ isGroup: isGroup,
+ groupName: bestAttemptContent.title
+ )
+
+ self.contentHandler?(self.finalContent ?? bestAttemptContent)
+ }
+ }
+
+ // MARK: - Shared Intent Logic
+
+ /// Shared method to create INPerson, INSendMessageIntent, and update the notification
+ private func activateCommunicationIntent(senderName: String, senderUsername: String, avatarData: Data?, conversationId: String, isGroup: Bool, groupName: String?) {
+ guard let bestAttemptContent = bestAttemptContent else { return }
+
+ // 1. Create Sender
+ var senderImage: INImage?
+ if let data = avatarData {
+ senderImage = INImage(imageData: data)
+ }
+
+ let sender = INPerson(
+ personHandle: INPersonHandle(value: senderUsername, type: .unknown),
+ nameComponents: nil,
+ displayName: senderName,
+ image: senderImage,
+ contactIdentifier: nil,
+ customIdentifier: nil
+ )
+
+ // 2. Handle Group Logic
+ var recipients: [INPerson]?
+ var speakableGroupName: INSpeakableString?
+
+ if isGroup {
+ speakableGroupName = (groupName != nil) ? INSpeakableString(spokenPhrase: groupName!) : nil
+ // Dummy recipient required for iOS to treat as group conversation
+ let dummy = INPerson(
+ personHandle: INPersonHandle(value: "placeholder", type: .unknown),
+ nameComponents: nil,
+ displayName: nil,
+ image: nil,
+ contactIdentifier: nil,
+ customIdentifier: "recipient_\(conversationId)"
+ )
+ recipients = [dummy]
+ }
+
+ // 3. Create Intent
+ let intent = INSendMessageIntent(
+ recipients: recipients,
+ outgoingMessageType: .outgoingMessageText,
+ content: bestAttemptContent.body,
+ speakableGroupName: speakableGroupName,
+ conversationIdentifier: conversationId,
+ serviceName: nil,
+ sender: sender,
+ attachments: nil
+ )
+
+ if isGroup {
+ intent.setImage(senderImage, forParameterNamed: \.speakableGroupName)
+ }
+
+ // 4. Donate & Update
+ let interaction = INInteraction(intent: intent, response: nil)
+ interaction.direction = .incoming
+ interaction.donate(completion: nil)
+
+ do {
+ self.finalContent = try bestAttemptContent.updating(from: intent)
+ } catch {
+ self.finalContent = bestAttemptContent
+ }
+ }
+
+ // MARK: - Helpers
+
+ private func fetchMessageContent(payload: Payload) {
+ UNUserNotificationCenter.current().getDeliveredNotifications { [weak self] deliveredNotifications in
+ guard let self = self else { return }
+
+ let identifiersToRemove = deliveredNotifications.filter {
+ $0.request.content.body == "You have a new message"
+ }.map { $0.request.identifier }
+
+ if identifiersToRemove.count > 0 {
+ UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiersToRemove)
+ }
+
+ // Request the content from server
+ if let messageId = payload.messageId {
+ self.rocketchat?.getPushWithId(messageId) { notification in
+ if let notification = notification {
+ // Set title and body first, processPayload will strip sender prefix for groups/channels
+ 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)
+ }
+ }
+ }
+ }
+
+ private func decryptMessage(payload: Payload, rid: String) -> String? {
+ if let content = payload.content, (content.algorithm == "rc.v1.aes-sha2" || content.algorithm == "rc.v2.aes-sha2") {
+ return rocketchat?.decryptContent(rid: rid, content: content)
+ } else if let msg = payload.msg, !msg.isEmpty {
+ return rocketchat?.decryptContent(rid: rid, content: EncryptedContent(algorithm: "rc.v1.aes-sha2", ciphertext: msg, kid: nil, iv: nil))
+ }
+ return nil
+ }
+
+ // MARK: - Avatar Fetching
+
+ /// Fetches avatar image data from a given avatar path
+ private func fetchAvatarDataFromPath(avatarPath: String, server: String, credentials: Credentials, completion: @escaping (Data?) -> Void) {
+ // URL-encode credentials to prevent malformed URLs if they contain special characters
+ guard let encodedToken = credentials.userToken.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
+ let encodedUserId = credentials.userId.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
+ completion(nil)
+ return
+ }
+
+ let fullPath = "\(avatarPath)?format=png&size=100&rc_token=\(encodedToken)&rc_uid=\(encodedUserId)"
+ guard let avatarURL = URL(string: server + fullPath) else {
+ completion(nil)
+ return
+ }
+
+ // Create URLSessionConfiguration with proper timeouts for notification service extension
+ // timeoutIntervalForResource ensures total download time is limited (not just inactivity)
+ let config = URLSessionConfiguration.default
+ config.timeoutIntervalForRequest = 3 // Inactivity timeout
+ config.timeoutIntervalForResource = 3 // Total download timeout (critical for notification extensions)
+ let session = URLSession(configuration: config)
+
+ var request = URLRequest(url: avatarURL)
+ request.httpMethod = "GET"
+ request.addValue(Bundle.userAgent, forHTTPHeaderField: "User-Agent")
+
+ let task = session.dataTask(with: request) { data, response, error in
+ guard error == nil,
+ let httpResponse = response as? HTTPURLResponse,
+ httpResponse.statusCode == 200,
+ let data = data else {
+ completion(nil)
+ return
}
+ completion(data)
+ }
+ task.resume()
+ }
+
+ /// Fetches avatar image data for video conference caller
+ func fetchCallerAvatarData(from payload: Payload, completion: @escaping (Data?) -> Void) {
+ let server = payload.host.removeTrailingSlash()
+ guard let credentials = Storage().getCredentials(server: server),
+ let username = payload.caller?.username,
+ let encoded = username.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
+ completion(nil)
+ return
+ }
+ fetchAvatarDataFromPath(avatarPath: "/avatar/\(encoded)", server: server, credentials: credentials, completion: completion)
+ }
+
+ /// Fetches avatar image data - sender's avatar for DMs, room avatar for groups/channels
+ func fetchAvatarData(from payload: Payload, completion: @escaping (Data?) -> Void) {
+ let server = payload.host.removeTrailingSlash()
+ guard let credentials = Storage().getCredentials(server: server) else {
+ completion(nil)
+ return
}
- if let bestAttemptContent = bestAttemptContent {
- contentHandler?(bestAttemptContent)
+ let avatarPath: String
+ if payload.type == .direct {
+ guard let username = payload.sender?.username,
+ let encoded = username.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
+ completion(nil)
+ return
+ }
+ avatarPath = "/avatar/\(encoded)"
+ } else {
+ guard let rid = payload.rid else {
+ completion(nil)
+ return
+ }
+ avatarPath = "/avatar/room/\(rid)"
}
+
+ fetchAvatarDataFromPath(avatarPath: avatarPath, server: server, credentials: credentials, completion: completion)
}
}
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 34cc3e5cd90..99a6053f60c 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -3157,7 +3157,7 @@ SPEC CHECKSUMS:
React-timing: 2d07431f1c1203c5b0aaa6dc7b5f503704519218
React-utils: 67cf7dcfc18aa4c56bec19e11886033bb057d9fa
ReactAppDependencyProvider: bf62814e0fde923f73fc64b7e82d76c63c284da9
- ReactCodegen: 8885059f55a205c667f52d6e35877137631116f2
+ ReactCodegen: 2f22969ab54e1aace69c9b5d3085e0a3b405a9a6
ReactCommon: 177fca841e97b2c0e288e86097b8be04c6e7ae36
RNBootSplash: 1280eeb18d887de0a45bb4923d4fc56f25c8b99c
RNCAsyncStorage: edb872909c88d8541c0bfade3f86cd7784a7c6b3
diff --git a/ios/ReplyNotification.swift b/ios/ReplyNotification.swift
index 4c3ea7c2183..f0318ddc8bd 100644
--- a/ios/ReplyNotification.swift
+++ b/ios/ReplyNotification.swift
@@ -88,26 +88,43 @@ class ReplyNotification: NSObject, UNUserNotificationCenterDelegate {
let ejsonData = ejsonString.data(using: .utf8),
let payload = try? JSONDecoder().decode(Payload.self, from: ejsonData),
let rid = payload.rid else {
+ // Show failure notification to user
+ let content = UNMutableNotificationContent()
+ content.body = "Failed to send reply. Invalid notification data."
+ let request = UNNotificationRequest(identifier: "replyPayloadFailure", content: content, trigger: nil)
+ UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
completionHandler()
return
}
let message = textResponse.userText
let rocketchat = RocketChat(server: payload.host.removeTrailingSlash())
- let backgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
+
+ var backgroundTask: UIBackgroundTaskIdentifier = .invalid
+ backgroundTask = UIApplication.shared.beginBackgroundTask {
+ // Expiration handler - called if system needs to reclaim resources
+ if backgroundTask != .invalid {
+ UIApplication.shared.endBackgroundTask(backgroundTask)
+ backgroundTask = .invalid
+ }
+ completionHandler()
+ }
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)
+ if backgroundTask != .invalid {
+ UIApplication.shared.endBackgroundTask(backgroundTask)
+ backgroundTask = .invalid
+ }
completionHandler()
}
guard let response = response, response.success else {
// Show failure notification
let content = UNMutableNotificationContent()
- content.body = "Failed to reply message."
+ content.body = "Failed to send reply."
let request = UNNotificationRequest(identifier: "replyFailure", content: content, trigger: nil)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
return
diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj
index d4e1efe2649..29cc9563241 100644
--- a/ios/RocketChatRN.xcodeproj/project.pbxproj
+++ b/ios/RocketChatRN.xcodeproj/project.pbxproj
@@ -7,7 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
- 0745F30D29A18A45DDDF8568 /* Pods_defaults_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5DA03EEFD8CEA0E9578CEFA /* Pods_defaults_NotificationService.framework */; };
+ 020BAAEF21339D1538B55D15 /* Pods_defaults_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 537F37501C643FE86F92916E /* Pods_defaults_NotificationService.framework */; };
+ 05F9D701BF644C25192B8E79 /* Pods_defaults_RocketChatRN.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A46AABC73B7E9703E69AF850 /* Pods_defaults_RocketChatRN.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 */; };
@@ -359,12 +360,11 @@
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 */; };
+ ACCF5C382F186B5D43B2C952 /* Pods_defaults_Rocket_Chat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85094D3182EA9331CC444733 /* Pods_defaults_Rocket_Chat.framework */; };
BC404914E86821389EEB543D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */; };
DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA7E862283664608B3894E34 /* libWatermelonDB.a */; };
/* End PBXBuildFile section */
@@ -613,8 +613,10 @@
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 = ""; };
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 = ""; };
+ 44A0BDC6158008BF9FF04F0A /* 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 = ""; };
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; };
+ 482E711ACFA5E2C4281835BF /* 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 = ""; };
+ 537F37501C643FE86F92916E /* Pods_defaults_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_NotificationService.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 = ""; };
@@ -622,14 +624,13 @@
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 = ""; };
- 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 = ""; };
+ 6CE8C7A54627937DB698E839 /* 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 = ""; };
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 = ""; };
@@ -639,19 +640,18 @@
7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
7ACFE7D82DDE48760090D9BC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AE10C0528A59530003593CB /* Inter.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Inter.ttf; sourceTree = ""; };
+ 85094D3182EA9331CC444733 /* Pods_defaults_Rocket_Chat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_Rocket_Chat.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 92B416D2ADF070E0E0C782A0 /* 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 = ""; };
9B215A42CFB843397273C7EA /* SecureStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = SecureStorage.m; sourceTree = ""; };
9B215A44CFB843397273C7EC /* MMKVBridge.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MMKVBridge.mm; path = Shared/RocketChat/MMKVBridge.mm; sourceTree = ""; };
+ A46AABC73B7E9703E69AF850 /* Pods_defaults_RocketChatRN.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_defaults_RocketChatRN.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A48B46D72D3FFBD200945489 /* A11yFlowModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = A11yFlowModule.h; sourceTree = ""; };
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 = ""; };
- 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 = ""; };
+ CD1F8BBE6FE382D33AB37935 /* 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 = ""; };
+ F6BFACDCE2AB06F4936B3E03 /* 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 */,
- 8BC28DFD84976599F4DD0E1F /* Pods_defaults_RocketChatRN.framework in Frameworks */,
+ 05F9D701BF644C25192B8E79 /* Pods_defaults_RocketChatRN.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -694,7 +694,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 0745F30D29A18A45DDDF8568 /* Pods_defaults_NotificationService.framework in Frameworks */,
+ 020BAAEF21339D1538B55D15 /* 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 */,
- 815F9657A87D16E93AD8451E /* Pods_defaults_Rocket_Chat.framework in Frameworks */,
+ ACCF5C382F186B5D43B2C952 /* Pods_defaults_Rocket_Chat.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1143,12 +1143,12 @@
7AC2B09613AA7C3FEBAC9F57 /* Pods */ = {
isa = PBXGroup;
children = (
- 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 */,
+ 6CE8C7A54627937DB698E839 /* Pods-defaults-NotificationService.debug.xcconfig */,
+ 44A0BDC6158008BF9FF04F0A /* Pods-defaults-NotificationService.release.xcconfig */,
+ 92B416D2ADF070E0E0C782A0 /* Pods-defaults-Rocket.Chat.debug.xcconfig */,
+ CD1F8BBE6FE382D33AB37935 /* Pods-defaults-Rocket.Chat.release.xcconfig */,
+ 482E711ACFA5E2C4281835BF /* Pods-defaults-RocketChatRN.debug.xcconfig */,
+ F6BFACDCE2AB06F4936B3E03 /* Pods-defaults-RocketChatRN.release.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -1247,9 +1247,9 @@
7ACD4853222860DE00442C55 /* JavaScriptCore.framework */,
B37C79D9BD0742CE936B6982 /* libc++.tbd */,
06BB44DD4855498082A744AD /* libz.tbd */,
- B5DA03EEFD8CEA0E9578CEFA /* Pods_defaults_NotificationService.framework */,
- CF268DB43E067211CC4AB1D8 /* Pods_defaults_Rocket_Chat.framework */,
- 4C9D354D4BED64C03F5586CC /* Pods_defaults_RocketChatRN.framework */,
+ 537F37501C643FE86F92916E /* Pods_defaults_NotificationService.framework */,
+ 85094D3182EA9331CC444733 /* Pods_defaults_Rocket_Chat.framework */,
+ A46AABC73B7E9703E69AF850 /* Pods_defaults_RocketChatRN.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -1269,9 +1269,8 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RocketChatRN" */;
buildPhases = (
- C0B975AF6ED607297F8F55F4 /* [CP] Check Pods Manifest.lock */,
- 7AA5C63E23E30D110005C4A7 /* Start Packager */,
- 589729E8381BA997CD19EF19 /* [Expo] Configure project */,
+ 8A4915EBB9B9EA919C35752B /* [CP] Check Pods Manifest.lock */,
+ 06C10D4F29CD7532492AD29E /* [Expo] Configure project */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
@@ -1280,10 +1279,8 @@
1E1EA8082326CCE300E22452 /* ShellScript */,
1ED0389C2B507B4F00C007D4 /* Embed Watch Content */,
7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */,
- 407D3EDE3DABEE15D27BD87D /* ShellScript */,
- 9C104B12BEE385F7555E641F /* [Expo] Configure project */,
- 69EE0EAB4655CCB0698B6026 /* [CP] Embed Pods Frameworks */,
- 4EF35507D275D88665224EED /* [CP] Copy Pods Resources */,
+ BCA373A96A91C30B09231381 /* [CP] Embed Pods Frameworks */,
+ E57DE3ACCEF8E313DFF4D411 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1351,12 +1348,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 1EFEB5A02493B6640072EDC0 /* Build configuration list for PBXNativeTarget "NotificationService" */;
buildPhases = (
- EBDF1B5B8303C6FF72717B0B /* [CP] Check Pods Manifest.lock */,
+ 664B079CA244E1ABB144A1C9 /* [CP] Check Pods Manifest.lock */,
86A998705576AFA7CE938617 /* [Expo] Configure project */,
1EFEB5912493B6640072EDC0 /* Sources */,
1EFEB5922493B6640072EDC0 /* Frameworks */,
1EFEB5932493B6640072EDC0 /* Resources */,
- B4801301A00C50FA3AD72CF9 /* [CP] Copy Pods Resources */,
+ 6BC31F4F1CF6E74A7683D2D4 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1371,19 +1368,18 @@
isa = PBXNativeTarget;
buildConfigurationList = 7AAB3E4F257E6A6E00707CF6 /* Build configuration list for PBXNativeTarget "Rocket.Chat" */;
buildPhases = (
- C32210C70D1F9214A2DE8E19 /* [CP] Check Pods Manifest.lock */,
- 7AAB3E13257E6A6E00707CF6 /* Start Packager */,
- 84028E94C77DEBDD5200728D /* [Expo] Configure project */,
+ 7E2F5D729E377A4A5D1E96B4 /* [CP] Check Pods Manifest.lock */,
+ 6319FBDD06EF0030B48AD389 /* [Expo] Configure project */,
7AAB3E14257E6A6E00707CF6 /* Sources */,
7AAB3E32257E6A6E00707CF6 /* Frameworks */,
7AAB3E41257E6A6E00707CF6 /* Resources */,
- 7AAB3E46257E6A6E00707CF6 /* Bundle React Native code and images */,
+ 7A55BE3B2F11316900D8744D /* Bundle React Native code and images */,
7AAB3E48257E6A6E00707CF6 /* Embed App Extensions */,
- 7AAB3E4B257E6A6E00707CF6 /* ShellScript */,
+ 7A55BE3C2F1131C000D8744D /* ShellScript */,
1ED1ECE32B8699DD00F6620C /* Embed Watch Content */,
- 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */,
- F55B2F4877AB3302D8608673 /* [CP] Embed Pods Frameworks */,
- 7B5EE97580C4626E59AEA53C /* [CP] Copy Pods Resources */,
+ 7A55BE3D2F11320C00D8744D /* Upload source maps to Bugsnag */,
+ F1B660D44BA706C4AA9FC60C /* [CP] Embed Pods Frameworks */,
+ 441DA981C582EB474E37526F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1557,8 +1553,9 @@
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";
};
- 1E1EA8082326CCE300E22452 /* ShellScript */ = {
+ 06C10D4F29CD7532492AD29E /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -1566,39 +1563,39 @@
);
inputPaths = (
);
+ name = "[Expo] Configure project";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
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";
+ 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";
};
- 407D3EDE3DABEE15D27BD87D /* ShellScript */ = {
+ 1E1EA8082326CCE300E22452 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
+ inputFileListPaths = (
+ );
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",
+ );
+ outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
+ 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";
};
- 4EF35507D275D88665224EED /* [CP] Copy Pods Resources */ = {
+ 441DA981C582EB474E37526F /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-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",
@@ -1665,10 +1662,10 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- 589729E8381BA997CD19EF19 /* [Expo] Configure project */ = {
+ 6319FBDD06EF0030B48AD389 /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
@@ -1685,46 +1682,107 @@
);
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-RocketChatRN/expo-configure-project.sh\"\n";
+ 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";
};
- 69EE0EAB4655CCB0698B6026 /* [CP] Embed Pods Frameworks */ = {
+ 664B079CA244E1ABB144A1C9 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
+ inputFileListPaths = (
+ );
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",
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
);
- 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-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.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;
};
- 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */ = {
+ 6BC31F4F1CF6E74A7683D2D4 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
- inputFileListPaths = (
- );
inputPaths = (
- "$TARGET_BUILD_DIR/$INFOPLIST_PATH",
- );
- name = "Upload source maps to Bugsnag";
- outputFileListPaths = (
+ "${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",
+ "${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 = (
+ "${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 = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n";
+ showEnvVarsInLog = 0;
};
- 7AA5C63E23E30D110005C4A7 /* Start Packager */ = {
+ 7A55BE3B2F11316900D8744D /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1733,16 +1791,16 @@
);
inputPaths = (
);
- name = "Start Packager";
+ name = "Bundle React Native code and images";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\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";
};
- 7AAB3E13257E6A6E00707CF6 /* Start Packager */ = {
+ 7A55BE3C2F1131C000D8744D /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1751,30 +1809,15 @@
);
inputPaths = (
);
- name = "Start Packager";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
- };
- 7AAB3E46257E6A6E00707CF6 /* Bundle React Native code and images */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 12;
- files = (
- );
- inputPaths = (
- );
- name = "Bundle React Native code and images";
- outputPaths = (
- );
- 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 = "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";
};
- 7AAB3E4B257E6A6E00707CF6 /* ShellScript */ = {
+ 7A55BE3D2F11320C00D8744D /* Upload source maps to Bugsnag */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1782,14 +1825,17 @@
inputFileListPaths = (
);
inputPaths = (
+ "$TARGET_BUILD_DIR/$INFOPLIST_PATH",
);
+ name = "Upload source maps to Bugsnag";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
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";
+ shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n";
+ showEnvVarsInLog = 0;
};
7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */ = {
isa = PBXShellScriptBuildPhase;
@@ -1809,84 +1855,31 @@
runOnlyForDeploymentPostprocessing = 0;
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";
+ showEnvVarsInLog = 0;
};
- 7B5EE97580C4626E59AEA53C /* [CP] Copy Pods Resources */ = {
+ 7E2F5D729E377A4A5D1E96B4 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
+ inputFileListPaths = (
+ );
inputPaths = (
- "${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",
- "${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}/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",
+ "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-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;
};
- 84028E94C77DEBDD5200728D /* [Expo] Configure project */ = {
+ 86A998705576AFA7CE938617 /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
@@ -1903,53 +1896,55 @@
);
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";
+ 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";
};
- 86A998705576AFA7CE938617 /* [Expo] Configure project */ = {
+ 8A4915EBB9B9EA919C35752B /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
);
- name = "[Expo] Configure project";
+ name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt",
);
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";
+ 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;
};
- 9C104B12BEE385F7555E641F /* [Expo] Configure project */ = {
+ BCA373A96A91C30B09231381 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
- inputFileListPaths = (
- );
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 = "[Expo] Configure project";
- outputFileListPaths = (
- );
+ name = "[CP] Embed Pods Frameworks";
outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
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";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
};
- B4801301A00C50FA3AD72CF9 /* [CP] Copy Pods Resources */ = {
+ E57DE3ACCEF8E313DFF4D411 /* [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-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",
@@ -2016,76 +2011,10 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- C0B975AF6ED607297F8F55F4 /* [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-RocketChatRN-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;
- };
- C32210C70D1F9214A2DE8E19 /* [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-Rocket.Chat-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;
- };
- 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";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- F55B2F4877AB3302D8608673 /* [CP] Embed Pods Frameworks */ = {
+ F1B660D44BA706C4AA9FC60C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -2492,7 +2421,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = CC5834318D0A8AF03D8124DB /* Pods-defaults-RocketChatRN.debug.xcconfig */;
+ baseConfigurationReference = 482E711ACFA5E2C4281835BF /* Pods-defaults-RocketChatRN.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
@@ -2557,7 +2486,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = F35C8301F7A5B8286AC64516 /* Pods-defaults-RocketChatRN.release.xcconfig */;
+ baseConfigurationReference = F6BFACDCE2AB06F4936B3E03 /* Pods-defaults-RocketChatRN.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
@@ -2972,7 +2901,7 @@
};
1EFEB59D2493B6640072EDC0 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7A6B8ACA1953C727CACE14EB /* Pods-defaults-NotificationService.debug.xcconfig */;
+ baseConfigurationReference = 6CE8C7A54627937DB698E839 /* Pods-defaults-NotificationService.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)";
CLANG_ANALYZER_NONNULL = YES;
@@ -3006,7 +2935,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 4.68.2;
+ MARKETING_VERSION = 4.69.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
@@ -3024,7 +2953,7 @@
};
1EFEB59E2493B6640072EDC0 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E06AA2822D8D24C3AA3C8711 /* Pods-defaults-NotificationService.release.xcconfig */;
+ baseConfigurationReference = 44A0BDC6158008BF9FF04F0A /* Pods-defaults-NotificationService.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)";
CLANG_ANALYZER_NONNULL = YES;
@@ -3059,7 +2988,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 4.68.2;
+ MARKETING_VERSION = 4.69.0;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
@@ -3075,7 +3004,7 @@
};
7AAB3E50257E6A6E00707CF6 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E023B58716C64D2BFB8C0681 /* Pods-defaults-Rocket.Chat.debug.xcconfig */;
+ baseConfigurationReference = 92B416D2ADF070E0E0C782A0 /* Pods-defaults-Rocket.Chat.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
@@ -3090,7 +3019,7 @@
DEVELOPMENT_TEAM = S6UPZG7ZR3;
ENABLE_BITCODE = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
- "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
+ EXCLUDED_ARCHS = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
@@ -3140,7 +3069,7 @@
};
7AAB3E51257E6A6E00707CF6 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7065C6880465E9A8735AA5EF /* Pods-defaults-Rocket.Chat.release.xcconfig */;
+ baseConfigurationReference = CD1F8BBE6FE382D33AB37935 /* Pods-defaults-Rocket.Chat.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist
index cf9e141f0ca..b2d1a400cba 100644
--- a/ios/RocketChatRN/Info.plist
+++ b/ios/RocketChatRN/Info.plist
@@ -28,7 +28,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 4.68.2
+ 4.69.0
CFBundleSignature
????
CFBundleURLTypes
@@ -108,5 +108,9 @@
UIViewControllerBasedStatusBarAppearance
+ NSUserActivityTypes
+
+ INSendMessageIntent
+
diff --git a/ios/RocketChatRN/RocketChatRN.entitlements b/ios/RocketChatRN/RocketChatRN.entitlements
index 3c6b134acbc..c7f9f603a67 100644
--- a/ios/RocketChatRN/RocketChatRN.entitlements
+++ b/ios/RocketChatRN/RocketChatRN.entitlements
@@ -12,6 +12,8 @@
applinks:go.rocket.chat
+ com.apple.developer.usernotifications.communication
+
com.apple.security.application-groups
group.ios.chat.rocket
diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist
index a54e61a991c..322edff5df4 100644
--- a/ios/ShareRocketChatRN/Info.plist
+++ b/ios/ShareRocketChatRN/Info.plist
@@ -26,7 +26,7 @@
CFBundlePackageType
XPC!
CFBundleShortVersionString
- 4.68.2
+ 4.69.0
CFBundleVersion
1
KeychainGroup
diff --git a/ios/Shared/Extensions/Bundle+Extensions.swift b/ios/Shared/Extensions/Bundle+Extensions.swift
index 8873e7b2c93..a3cf7297de3 100644
--- a/ios/Shared/Extensions/Bundle+Extensions.swift
+++ b/ios/Shared/Extensions/Bundle+Extensions.swift
@@ -12,4 +12,13 @@ extension Bundle {
return string
}
+
+ /// Returns User-Agent string for API requests: "RC Mobile; ios {version}; v{appVersion} ({build})"
+ static 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/Shared/Models/Payload.swift b/ios/Shared/Models/Payload.swift
index 2a91f3a1b38..7e64586569b 100644
--- a/ios/Shared/Models/Payload.swift
+++ b/ios/Shared/Models/Payload.swift
@@ -11,6 +11,7 @@ import Foundation
struct Caller: Codable {
let _id: String?
let name: String?
+ let username: String?
}
struct Payload: Codable {
diff --git a/ios/Shared/RocketChat/API/Request.swift b/ios/Shared/RocketChat/API/Request.swift
index 97c54bf8574..f28d7e1ee81 100644
--- a/ios/Shared/RocketChat/API/Request.swift
+++ b/ios/Shared/RocketChat/API/Request.swift
@@ -55,7 +55,7 @@ extension Request {
request.httpMethod = method.rawValue
request.httpBody = body()
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
- request.addValue(userAgent, forHTTPHeaderField: "User-Agent")
+ request.addValue(Bundle.userAgent, forHTTPHeaderField: "User-Agent")
if let userId = api.credentials?.userId {
request.addValue(userId, forHTTPHeaderField: "x-user-id")
@@ -70,14 +70,4 @@ 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 80a43c7f587..6f52b0e2f6b 100644
--- a/ios/fastlane/Fastfile
+++ b/ios/fastlane/Fastfile
@@ -29,17 +29,35 @@ platform :ios do
desc "Submit a new Beta Build to Apple TestFlight"
lane :beta do |options|
+ changelog_path = File.expand_path('../../changelog.txt', __dir__)
+
+ changelog = if File.exist?(changelog_path)
+ content = File.read(changelog_path)
+ content.length > 4000 ? content[0, 3997] + "..." : content
+ end
+
api_key = app_store_connect_api_key(
key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
issuer_id: ENV["APP_STORE_CONNECT_API_KEY_ISSUER_ID"],
key_filepath: 'fastlane/app_store_connect_api_key.p8',
in_house: false
)
- pilot(
- ipa: 'Rocket.Chat.ipa',
- app_identifier: options[:official] ? 'chat.rocket.ios' : 'chat.rocket.reactnative',
- skip_waiting_for_build_processing: true
- )
+
+ pilot_options = {
+ ipa: 'Rocket.Chat.ipa',
+ app_identifier: options[:official] ? 'chat.rocket.ios' : 'chat.rocket.reactnative',
+ skip_waiting_for_build_processing: !(options[:official] && changelog),
+ reject_build_waiting_for_review: true,
+ }
+
+ if options[:official] && changelog
+ pilot_options[:changelog] = changelog
+ pilot_options[:distribute_external] = true
+ pilot_options[:notify_external_testers] = true
+ pilot_options[:groups] = ["External Testers"]
+ end
+
+ pilot(pilot_options)
upload_symbols_to_crashlytics(dsym_path: "Rocket.Chat.app.dSYM.zip")
upload_symbols_to_bugsnag(
config_file: "RocketChatRN/Info.plist",
diff --git a/package.json b/package.json
index 69549d4a88d..45c7b5e50e1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rocket-chat-reactnative",
- "version": "4.68.2",
+ "version": "4.69.0",
"private": true,
"packageManager": "yarn@1.22.22",
"scripts": {
diff --git a/yarn.lock b/yarn.lock
index 5f334cbad37..4a19e871830 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5024,8 +5024,8 @@
tldts "~5.7.112"
"@rocket.chat/mobile-crypto@RocketChat/rocket.chat-mobile-crypto":
- version "0.2.0"
- resolved "https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/06bab0cb2e329822911c80d1b937219c15154faa"
+ version "0.2.1"
+ resolved "https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/b75e261282bc0c25a3b8fde3230d1c1e01809e00"
"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile":
version "1.3.3-mobile"