Skip to content

Conversation

@OtavioStasiak
Copy link
Contributor

@OtavioStasiak OtavioStasiak commented Oct 22, 2025

Proposed changes

This PR migrates our storage implementation from react-native-mmkv-storage to react-native-mmkv.
The new library is more actively maintained and provides improved stability and performance.

This migration may also resolve issues previously reported on iOS, such as unexpected user logouts.

Issue(s)

https://rocketchat.atlassian.net/browse/NATIVE-1064

How to test or reproduce

New Installation

Default user

  • Log in to the app.
  • Close the app.
  • Open it again (the user must remain logged in).
  • Send a message, edit a message, and react to a message (just to ensure everything is working as expected).

Server with SSL Certificate

  • Log in using an SSL certificate.
  • Close the app.
  • Open it again (the user must remain logged in).
  • Try to open an attachment (image and PDF).

E2EE

  • Add an E2EE password.
  • Send a message in an encrypted room.
  • Close the app.
  • Open it again.
  • E2EE must be working.

Notifications (This case must be repeated in Default user, SSL Server and E2EE)

  • Open the app.
  • Receive a notification (it should appear as an in-app notification).
  • Close the app.
  • Receive a notification (it should appear as a push notification).

Upgrading the app

For the upgrade process, all tests performed in New Installation must be repeated, but under different scenarios:

  • Upgrading the app while logged in to a single server.
  • Upgrading the app while logged in to a server with E2EE enabled.
  • Upgrading the app while logged in to a server with SSL Pinning.
  • Upgrading the app while logged in to multiple servers.
  • Upgrading the app while logged in to multiple servers, where the unfocused server has E2EE enabled.
  • Upgrading the app while logged in to multiple servers, where the unfocused server has SSL Pinning.

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • New Features

    • Automatic migration from legacy encrypted storage to new MMKV-backed storage on app startup
    • Platform-native secure key storage (Keychain/Keystore) with migration tooling and a native reader to list/import existing storage
    • Android WebView: certificate alias support for client cert selection
  • Bug Fixes

    • Safer null/undefined handling in UI pickers
    • Improved async SSL certificate persistence and playback speed type handling
  • Refactor

    • Replaced legacy storage integration with a unified MMKV bridge and reorganized initialization across platforms

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

Deleted legacy react-native-mmkv-storage mocks and integrated react-native-mmkv with native MMKVBridge/TurboModule readers, added cross-platform SecureStorage/SecureKeystore modules, implemented MMKV migration flows, updated app initialization and preferences, and adjusted native build/project configs for MMKV integration.

Changes

Cohort / File(s) Change Summary
Mocks & JS MMKV
__mocks__/react-native-mmkv-storage.js, __mocks__/react-native-mmkv.js, patches/react-native-mmkv+3.3.3.patch, package.json
Removed old react-native-mmkv-storage mock; added in-memory react-native-mmkv mock (shared-per-id, listeners, typed getters/setters); disabled test mock and replaced dependency with [email protected].
JS Preferences & Migration
app/lib/methods/userPreferences.ts, app/lib/methods/migrateMMKVStorage.ts, app/lib/methods/helpers/getSecureKey.ts, app/lib/native/NativeMMKVReader*.ts, app/index.tsx
Replaced react-native-mmkv-storage usage with react-native-mmkv APIs; added storage initialization, generic useUserPreferences hook, migrateFromOldMMKV migration, SecureStorage helper, and Native/TurboModule wrappers; migration invoked at startup.
JS Helpers / Init
app/sagas/init.js, app/lib/helpers/ensureServersInDatabase.ts
Added ensureServersInDatabase and run it after migration; removed a stray console.log.
TypeScript safety fixes
app/containers/AudioPlayer/PlaybackSpeed.tsx, app/views/*
Minor type casts and null-coalescing changes to prevent undefined values.
Android: MMKV Reader & Packages
android/.../storage/NativeMMKVReaderSpec.java, MMKVReaderTurboModule.java, MMKVReaderTurboPackage.java, android/.../MainApplicationModuleProvider.cpp
Added TurboModule spec, implementation and package to read/decrypt legacy MMKV files; exposed MMKVReader in native provider.
Android: Secure Storage & Keystore
android/.../SecureStorage.java, SecureStoragePackage.java, storage/SecureKeystore.java, storage/Storage.java, storage/Constants.java
New SecureStorage native module (AES-GCM + Android Keystore wrapper), package, SecureKeystore fallback, storage utilities and constants for secure key management.
Android: Integration & Gradle
android/app/build.gradle, android/app/src/main/java/.../MainApplication.kt, .../notification/Ejson.java
Added androidx.security dependency; registered MMKVReaderTurboPackage and initialized MMKV; switched notification code to use SecureStorage.
iOS: MMKV Bridge & Migration
ios/Shared/RocketChat/MMKVBridge.*, ios/MMKVMigration.{h,mm}, ios/Shared/RocketChat/MMKV.swift, ios/Shared/RocketChat/ClientSSL.swift, ios/SSLPinning*
Added MMKVBridge Objective-C/C++ bridge, MMKVMigration migrator; switched MMKV consumers (storage, SSL pinning) to MMKVBridge and updated initialization/migration order.
iOS: SecureStorage (Keychain)
ios/SecureStorage.h, ios/SecureStorage.m, bridging headers, Podfile, project.pbxproj`
New SecureStorage native module exposing sync/async keychain APIs; updated bridging headers, Podfile and Xcode project to include new sources and build flags (FORCE_POSIX).
iOS: App init changes
ios/AppDelegate.swift, ios/NotificationService/NotificationService-Bridging-Header.h, ios/RocketChatRN-Bridging-Header.h
Invoke MMKVMigration early during startup, rework MMKV initialization to use MMKVBridge, and adjust header imports to local bridge files.
Patches & Metro
patches/react-native-mmkv-storage+12.0.0.patch, patches/react-native-webview+13.15.0.patch, metro.config.js
Applied patches (removed UIProtectedData wait, AppGroup fallback), added webview certificate-alias hooks and MMKV pod touchpoints; adjusted metro E2E mock toggle behavior.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Startup
    participant Migrator as migrateFromOldMMKV()
    participant Turbo as NativeMMKVReader (TurboModule)
    participant Old as Legacy MMKV
    participant New as react-native-mmkv / MMKVBridge
    App->>Migrator: call migrateFromOldMMKV()
    Migrator->>Turbo: readAndDecryptMMKV(instanceId)
    Turbo->>Old: open instance (with/without password) and read keys
    Old-->>Turbo: key-value map
    Turbo-->>Migrator: decrypted data
    Migrator->>New: write keys by type (string/number/bool)
    New-->>Migrator: ack per-key
    Migrator->>App: mark WORKSPACE_MIGRATION_COMPLETED
Loading
sequenceDiagram
    participant JS as JS getSecureKey()
    participant Native as SecureStorage (native)
    participant Store as Keychain/SharedPrefs
    JS->>Native: getSecureKey(hexAlias)
    Native->>Store: fetch stored Base64(iv+ciphertext)
    alt stored value exists
      Native->>Native: Base64 decode, split IV, decrypt with AES-GCM
      Native-->>JS: plaintext or null
    else missing
      Native-->>JS: null
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–75 minutes

Areas needing extra attention:

  • SecureStorage / SecureKeystore cryptography and IV/key lifecycle (Android/iOS).
  • MMKVBridge native init (cryptKey, rootPath, app group paths) and correctness across platforms.
  • Migration logic: alias hexing, decrypt fallback attempts, per-key type mapping when writing into new MMKV.
  • TurboModule integration, JNI/JSI codegen artifacts and provider wiring.
  • iOS project.pbxproj and Podfile changes that alter build flags and header search paths.

Possibly related PRs

Suggested reviewers

  • diegolmello

Poem

🐇
I nudge old keys from dark MMKV nests,
hop bridges of bytes to newer chests,
whisper secrets to keystores deep,
migrate and guard while others sleep,
carrots, code, and safe-token rests.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.92% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the pull request: migrating from react-native-mmkv-storage to react-native-mmkv. It is concise, specific, and clearly communicates the primary change.
Linked Issues check ✅ Passed The pull request successfully implements the migration from react-native-mmkv-storage to react-native-mmkv across both Android and iOS platforms with data migration workflows, securing storage with encryption, and maintaining feature parity for login persistence, SSL certificates, E2EE, and notifications NATIVE-1064.
Out of Scope Changes check ✅ Passed Minor type casting improvements in PlaybackSpeed.tsx and MediaAutoDownloadView.tsx, plus enum value handling in AccessibilityAndAppearanceView.tsx, are technically out-of-scope improvements but negligible and do not detract from the primary migration objective.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat.migrate-react-native-mmkv-storage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
patches/react-native-webview+13.15.0.patch (2)

55-60: Handle client certificate failure path and nulls in onReceivedClientCertRequest

The new client‑cert handling is on the right track, but there are two important gaps:

  • If KeyChain.getPrivateKey / getCertificateChain fails or returns null, doInBackground returns null and onPostExecute does nothing. That means neither request.proceed, request.cancel, nor request.ignore is ever called, which can leave the TLS handshake hanging or failing in undefined ways.
  • privKey or certChain can individually be null; you should guard against that before calling request.proceed.

Consider something along these lines:

-                protected void onPostExecute(SslStuff sslStuff) {
-                    if (sslStuff != null) {
-                        request.proceed(sslStuff.privKey, sslStuff.certChain);
-                    }
-                }
+                protected void onPostExecute(SslStuff sslStuff) {
+                    if (sslStuff != null && sslStuff.privKey != null && sslStuff.certChain != null) {
+                        request.proceed(sslStuff.privKey, sslStuff.certChain);
+                    } else {
+                        request.cancel(); // or super.onReceivedClientCertRequest(view, request);
+                    }
+                }

Also, logging the exception in doInBackground (instead of silently returning null) would greatly help diagnosing cert/KeyChain issues.

Also applies to: 67-122


40-46: Fix critical typo and resolve missing ReactContext method

Two blocking issues in the patch:

  1. Critical typo in node_modules/react-native-webview/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewModule.java line ~43: mRNCWxebViewModuleImpl should be mRNCWebViewModuleImpl. This causes a compile error.

  2. Missing method: Line 43 in RNCWebView.java calls this.getReactApplicationContext(), but RNCWebView extends WebView which doesn't provide this method. Either add getReactApplicationContext() to RNCWebView or pass ReactContext via constructor/field.

  3. Missing error handling in RNCWebViewClient.onReceivedClientCertRequest(): When KeyChain operations fail and sslStuff is null, the code does nothing in onPostExecute(). Add request.cancel() or request.ignore() to properly reject the cert request on failure.

ios/RocketChatRN.xcodeproj/project.pbxproj (1)

274-291: SecureStorage.m / MMKVBridge.mm appear multiple times in Sources; likely duplicate‑symbol linker errors

Right now the same physical files seem to be wired into certain targets more than once:

  • Multiple PBXFileReference/PBXBuildFile entries for SecureStorage.m and MMKVBridge.mm (e.g., 66C2701F… SecureStorage.m, 9B215A42… SecureStorage.m, and their various … in Sources build files).
  • In 13B07F871A680F5B00A75B9A /* Sources */ (RocketChatRN), both 66C270212E… /* SecureStorage.m in Sources */ and A2C6E2DD38… /* SecureStorage.m in Sources */ are included.
  • In 7AAB3E14257E6A6E00707CF6 /* Sources */ (Rocket.Chat), you also have both 66C270202E… and 79D8C97F8C… entries for SecureStorage.m.
  • Similar duplication pattern exists for MMKVBridge.mm across PBXBuildFile entries and multiple Sources phases.

If these entries all point at the same .m/.mm files (which they appear to), Xcode will compile them twice for a given target and you’ll hit duplicate symbol errors at link time.

Recommendation:

  • For each target, ensure at most one … in Sources entry per actual file (one BuildFile per FileReference per target).
  • Prefer a single canonical FileReference for SecureStorage.{h,m} and MMKVBridge.mm under the main group hierarchy, and drop the extra “Recovered References”/duplicate references.

Also applies to: 360-361, 587-590, 607-608, 612-613, 690-692, 709-710, 1143-1145, 1188-1193, 2146-2196, 2382-2418, 2421-2471

🧹 Nitpick comments (26)
app/lib/methods/helpers/getSecureKey.ts (2)

5-11: Align ISecureStorage typing with native module behavior

The TS interface models setSecureKey as Promise<void>, but the Android native implementation currently resolves a boolean (promise.resolve(true)). That’s harmless here (you ignore the return value), but it creates a subtle mismatch between JS typings and the native API and can confuse future callers.

Consider either:

  • Updating ISecureStorage.setSecureKey to something like Promise<boolean | void> or Promise<unknown>, or
  • Normalizing the native side to resolve null/void for symmetry.

Not urgent, but worth aligning to keep the bridge contract explicit.


43-55: Make getAppGroupPath resilient when SecureStorage is missing

Right now you only optional‑chain the method (SecureStorage.getAppGroupPath?.()), so if SecureStorage itself is undefined (e.g., certain test environments), you’ll hit a TypeError that is then caught and logged.

You can avoid even that by optional‑chaining the module as well:

-		const appGroupPath = await SecureStorage.getAppGroupPath?.();
+		const appGroupPath = await SecureStorage?.getAppGroupPath?.();

This keeps behavior the same (returns null when the module or method is absent) but avoids spurious errors in logs.

app/lib/methods/helpers/sslPinning.ts (1)

37-44: Add consistent null guard for hostname in removeCertificate.

For consistency with the defensive coding added to persistCertificate (lines 29-32), consider adding a null guard when calling extractHostname(server) on line 42.

Apply this diff:

 const removeCertificate = (server: string) => {
 	const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`);
 	if (certificate) {
 		UserPreferences.removeItem(certificate);
 	}
-	UserPreferences.removeItem(extractHostname(server));
+	const hostname = extractHostname(server);
+	if (hostname) {
+		UserPreferences.removeItem(hostname);
+	}
 	UserPreferences.removeItem(`${CERTIFICATE_KEY}-${server}`);
 };
app/views/MediaAutoDownloadView/index.tsx (1)

38-42: Explicit MediaDownloadOption casts are fine; consider tightening types upstream

These casts are safe given the useUserPreferences<MediaDownloadOption> usage and help the compiler, but if we see more of these it may be worth adjusting ListPicker (or useUserPreferences) prop/return types so the union flows through without needing as at call sites.

ios/Podfile (1)

69-92: Post_install MMKV/webview tweaks are reasonable; consider minor guards

The additional HEADER_SEARCH_PATHS for react-native-webview and FORCE_POSIX=1 defines for react-native-mmkv and the app targets are a pragmatic way to make the MMKV C++ API available. Two small considerations:

  • You might want to guard the extra header path with an include? check similar to the FORCE_POSIX block to avoid duplicates if post_install runs multiple times.
  • Since this touches Xcode build settings for multiple targets, please verify both Debug/Release builds on CI to ensure there are no unexpected warnings or conflicts.
patches/react-native-mmkv+3.3.3.patch (1)

785-811: MMKV now always uses the real native instance; ensure tests/mocks are aligned

This patch removes the isTest() ? createMockMMKV() : ... branch and disables createMMKV.mock.ts, forcing MMKV to always call createMMKV(configuration). That’s consistent with the goal of exercising real MMKV in E2E, but it means:

  • Unit tests and any non‑RN environments must rely entirely on the new Jest manual mock for react-native-mmkv; otherwise, createMMKV will hit native APIs and fail in Node.
  • The isTest import in MMKV.ts is now unused and could be dropped in a follow‑up patch to keep the diff minimal.

Please confirm the full Jest suite and E2E runs pass with this behavior before merging.

android/app/src/main/java/chat/rocket/reactnative/storage/SecureKeystore.java (1)

37-257: SecureKeystore port looks correct; a couple of crypto/compatibility details to double-check

This class closely mirrors the original react-native-mmkv-storage keystore helper and the useKeystore() branching (Keystore path for API ≥ M, SharedPreferences/EncryptedSharedPreferences for older devices) is consistent.

Two things worth verifying/tidying up:

  1. EncryptedSharedPreferences on older APIs
    In the constructor, EncryptedSharedPreferences is used when !useKeystore() (API < M) and any failure falls back to standard SharedPreferences. Given androidx.security:security-crypto’s API level expectations, please confirm this behaves as intended on your actual minSdk (or that, in practice, the branch is never hit because minSdk ≥ 23).

  2. SecretKeySpec algorithm string
    new SecretKeySpec(..., Constants.AES_ALGORITHM) currently passes the full transformation string ("AES/ECB/PKCS5Padding") as the algorithm name. The usual pattern is a bare "AES" algorithm identifier; using the transformation here can be brittle across providers. Consider introducing a separate AES_KEY_ALGORITHM = "AES" (or similar) in Constants and using that for SecretKeySpec, while keeping AES_ALGORITHM for Cipher.getInstance(...).

Neither point is strictly blocking, but they’re easier to adjust now than after this becomes widely deployed.

__mocks__/react-native-mmkv.js (1)

3-93: MMKV mock behavior looks correct; consider minor test-friendly tweaks

The mock captures the shared-per-id storage and listener API well and should be sufficient for most tests. Two small, optional improvements you might consider later:

  • In notifyListeners, iterate over a shallow copy ([...this.listeners]) to avoid edge cases where a listener removes itself or others while iterating.
  • If you ever need suite-level isolation, exposing a small internal reset helper (e.g., clearing storageInstances) can help keep Jest tests independent without relying on clearAll in app code.
app/index.tsx (1)

42-42: Good placement for migration; consider gating to avoid repeated work

Running await migrateFromOldMMKV() at the start of init ensures MMKV data is migrated before any local settings or notifications logic runs, which is the right ordering. Given the migration reads and rewrites the full MMKV payload, you may want to short‑circuit it after the first successful run (e.g., using the existing WORKSPACE_MIGRATION_COMPLETED flag) so upgraded users don’t pay the migration cost on every cold start.

Also applies to: 129-155

ios/SSLPinning.mm (1)

19-40: Consider caching MMKVBridge/SecureStorage instead of recreating each call

+getMMKVInstance constructs a new SecureStorage and MMKVBridge every time it is called (from both runChallenge and xxx_updateSecureStreamOptions). That’s likely more work than needed during connection setup.

You could cache a single shared instance, e.g.:

+ (MMKVBridge *)getMMKVInstance {
  static MMKVBridge *sharedInstance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    SecureStorage *secureStorage = [[SecureStorage alloc] init];
    NSString *hexKey = [self stringToHex:@"com.MMKV.default"];
    NSString *key = [secureStorage getSecureKey:hexKey];

    NSString *appGroupPath = nil;
    NSString *suite = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"];
    if (suite) {
      NSURL *directory = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:suite];
      appGroupPath = directory.path;
    }

    NSString *mmkvPath = appGroupPath ? [appGroupPath stringByAppendingPathComponent:@"mmkv"] : nil;
    NSData *cryptKey = (key.length > 0) ? [key dataUsingEncoding:NSUTF8StringEncoding] : nil;
    sharedInstance = [[MMKVBridge alloc] initWithID:@"default" cryptKey:cryptKey rootPath:mmkvPath];
  });
  return sharedInstance;
}
ios/Shared/RocketChat/MMKVBridge.h (1)

12-27: Optionally mark the custom initializer as designated and disable plain init

The bridge interface looks clean and the nullability annotations make sense. To prevent accidental use of [MMKVBridge init] without required parameters, you might make the custom initializer designated and mark init unavailable.

For example:

 @interface MMKVBridge : NSObject
 
 - (instancetype)initWithID:(NSString *)mmapID
                  cryptKey:(nullable NSData *)cryptKey
                  rootPath:(nullable NSString *)rootPath;
+
+- (instancetype)init NS_UNAVAILABLE;
+ + (instancetype)new NS_UNAVAILABLE;

(Optionally add NS_DESIGNATED_INITIALIZER to initWithID:cryptKey:rootPath:.)

android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboPackage.java (1)

19-44: TurboModule wiring looks correct; consider minor robustness tweaks.

The package correctly registers MMKVReader and exposes it as a TurboModule. Two small nits you may want to adjust:

  • To be null‑safe, prefer NativeMMKVReaderSpec.NAME.equals(name) instead of name.equals(...) in getModule, in case name is ever null.
  • In ReactModuleInfo, hasConstants is set to true, but this module appears to expose only methods (no getConstants() override). Unless you plan to add constants, setting this to false would more accurately reflect the module’s surface.
app/lib/methods/migrateMMKVStorage.ts (1)

8-9: Align migration result/metrics with actual behavior and clean up logging.

A few small inconsistencies here that are worth tightening up:

  • errors is accumulated but never returned or inspected by callers; it only drives logging.
  • The function returns a detailed object only in the “no data” case, but void on the successful migration path, which makes the API awkward if anyone ever wants to use the metrics.
  • The log message "Step 2: Migrating..." doesn’t match the preceding comment (// Step 3).

Consider one of these options:

  1. Make the function fire‑and‑forget:
    • Remove the structured return object and the errors array, leaving only logs and the preference flag, or
  2. Commit to a structured result:
    • Track keysFound, keysMigrated, and errors for all paths and return a consistent result type from the function.

For example:

-	const allKeys = Object.keys(oldData);
+	const allKeys = Object.keys(oldData);
+	const keysFound = allKeys.length;
+	let keysMigrated = 0;
@@
-		if (allKeys.length === 0) {
+		if (keysFound === 0) {
@@
-		for (const key of allKeys) {
+		for (const key of allKeys) {
@@
-					targetStorage.set(key, value);
+					targetStorage.set(key, value);
+					keysMigrated += 1;
@@
-		console.log('=== Migration Complete ===');
+		console.log('=== Migration Complete ===', { keysFound, keysMigrated, errors });
+		return {
+			success: errors.length === 0,
+			keysFound,
+			keysMigrated,
+			errors,
+			data: oldData
+		};

And align the comment/log:

-		// Step 3: Migrate data
-		console.log('Step 2: Migrating data to new MMKV...');
+		// Step 2: Migrate data
+		console.log('Step 2: Migrating data to new MMKV...');

Also applies to: 21-32, 41-58, 60-65

android/app/src/main/java/chat/rocket/reactnative/storage/NativeMMKVReaderSpec.java (1)

9-30: TurboModule spec surface is clear and consistent with the JS side.

The NativeMMKVReaderSpec definition (module name, constructor, and three Promise‑based @ReactMethods) looks correct for exposing readAndDecryptMMKV, listMMKVFiles, and getStoragePath to JS. As a small improvement, you may want to add brief JavaDoc comments describing the expected promise payloads (e.g., shape of oldData and path format) to keep the native/TS contract self‑documented.

android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (2)

71-85: Type probing logic can silently coerce unknown types to boolean false

For each key you:

  • try decodeString,
  • then decodeInt with Integer.MIN_VALUE sentinel,
  • else unconditionally decodeBool(key, false) and store the result.

Any value that is not a string or int (e.g. a stored double, bytes, or an unsupported type) will end up as false in the result map, which can be misleading during migration.

Consider:

  • Checking mmkv.containsKey(key) (if available) before defaulting to false, and/or
  • Skipping keys that can’t be decoded into the supported types instead of coercing them to boolean, or
  • Explicitly handling all known types you expect from the old storage.

Optionally, you can also remove stringCount, intCount, and boolCount since they’re unused, or surface them as debug metrics if they’re meant for logging.

Also applies to: 99-125


73-76: Minor: avoid repeated MMKV.initialize and confirm toHex matches upstream

Calling MMKV.initialize(reactContext) on every readAndDecryptMMKV invocation is redundant; it can typically be done once at app startup or cached behind a static guard.

Also, since toHex is meant to be “same as react-native-mmkv-storage”, please double‑check against the original implementation to ensure alias derivation is byte‑for‑byte identical across platforms (critical for finding existing keys).

Also applies to: 139-146

ios/SecureStorage.m (2)

73-92: Fix SecItemCopyMatching usage and release the returned CF object

searchKeychainCopyMatching::

  • Requests kSecReturnData, so result is a CFDataRef, not a dictionary.
  • Casts result to NSDictionary * and then passes it to initWithData:, which expects NSData * – works at runtime only because the dynamic type is actually data, but is type‑unsafe.
  • Does not check OSStatus and never releases result, leaking the returned data.

A safer pattern:

- (NSString *)searchKeychainCopyMatching:(NSString *)identifier {
  NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
  searchDictionary[(id)kSecMatchLimit] = (id)kSecMatchLimitOne;
  searchDictionary[(id)kSecReturnData] = (id)kCFBooleanTrue;

  CFTypeRef result = NULL;
  OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary, &result);
  if (status != errSecSuccess || result == NULL) {
    if (result != NULL) {
      CFRelease(result);
    }
    return nil;
  }

  NSData *data = (__bridge_transfer NSData *)result;
  return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}

This:

  • Uses the correct data type,
  • Checks status,
  • Uses __bridge_transfer to avoid a leak.

22-51: Minor: exceptions are swallowed without logging

setSecureKey:... and getSecureKey: catch NSException and either do nothing or return NULL, giving you no visibility into underlying keychain issues.

Consider at least logging the exception (e.g., NSLog or a centralized logger) so failures in secure storage can be diagnosed in the field.

Also applies to: 127-139

android/app/src/main/java/chat/rocket/reactnative/storage/Constants.java (1)

8-29: Constants look consistent with original library usage

Centralizing these MMKV/keystore constants is helpful and aligns with the upstream react-native-mmkv-storage implementation. No functional issues spotted here.

If you later revisit the encryption design, consider migrating away from ECB modes (AES/ECB/PKCS5Padding) toward an AEAD mode, but that’s out of scope for this migration.

ios/SecureStorage.h (1)

1-4: Minor: duplicate RCTBridgeModule import and header availability

You import <React/RCTBridgeModule.h> twice; the duplicate import is harmless but can be removed for clarity.

Static analysis is reporting 'React/RCTBridgeModule.h' file not found; this is usually a header‑search‑path or Pods configuration issue rather than a problem with this header itself. Just confirm that the React headers are available in your iOS build environment.

ios/MMKVMigration.mm (2)

87-107: Value-type handling is limited; NSNumber branch is currently unreachable

readInstanceData:withPassword:atPath: only ever populates data with:

  • NSString values from stringForKey:, or
  • NSData values from dataForKey:.

You never insert NSNumber values into data, so in +migrate the NSNumber case:

} else if ([value isKindOfClass:[NSNumber class]]) {
    [newMMKV setString:[value stringValue] forKey:key];
    migratedCount++;
}

is effectively unreachable.

Depending on how the old storage was used:

  • If you genuinely only stored strings and binary blobs, you can remove the NSNumber case to simplify the code.
  • If numeric/bool values were stored in MMKV, you may want to extend readInstanceData to read them via typed APIs (e.g., boolForKey:, intForKey:) and then preserve those types on write, instead of relying solely on string/data.

Also applies to: 149-185


63-82: Behavior note: merging multiple instances into one is last-writer-wins

The migration flattens all discovered MMKV instances into a single allData dictionary and then writes everything into a new "default" MMKV:

[allData addEntriesFromDictionary:instanceData];
...
for (NSString *key in allData) {
    id value = allData[key];
    ...
}

If the same key exists in multiple instances, the value from the last processed instance silently overrides earlier ones.

If this is intentional, consider adding a brief comment explaining the decision. If not, you might want to:

  • Track the source instance for each key, or
  • Define explicit precedence rules (e.g., prefer mmkvIDStore over default) and enforce them when merging.

Also applies to: 87-107, 125-139

android/app/src/main/java/chat/rocket/reactnative/SecureStorage.java (1)

24-31: Secure storage implementation looks solid; a couple of minor robustness tweaks possible

The overall design (AndroidKeyStore AES/GCM per-alias key, random IV, IV||ciphertext Base64 in SharedPreferences) is sound and appropriate for the intended use; the static-analysis hints about 3DES/ECB don’t apply here since you’re explicitly using AES/GCM/NoPadding.

Two small, optional hardening ideas:

  • In getSecureKeyInternal, you assume a 12‑byte IV; you could derive the IV length from combined.length (e.g., store IV length or use cipher.getBlockSize() at encryption time) to be more future‑proof.
  • In getSecureKeyInternal, you currently swallow all exceptions and return null; consider logging the alias or a more specific message to help debug corrupted entries (you already log in getSecureKey/setSecureKey, so even just reusing that style here would help).

Nothing blocking; this is good to ship as is.

Also applies to: 65-101, 104-161

patches/react-native-webview+13.15.0.patch (2)

67-83: Global static certificateAlias limits flexibility (OK if you only ever need one)

certificateAlias is static on RNCWebViewClient, so a single alias is shared by all WebViews. That matches your current API (a single setCertificateAlias static across managers/modules), but it makes per‑WebView or per‑host aliases impossible without refactoring.

If in future you need multiple aliases (e.g., multiple servers with different client certs), you’ll likely want to move this alias down to instance level on RNCWebViewClient and plumb it from JS per-WebView instead of globally.


1-34: Adding .project under node_modules/react-native-webview/android is IDE-specific

The new Eclipse .project metadata under node_modules/react-native-webview/android is harmless, but it’s tooling-specific and unusual to carry in a long‑lived patch. If you don’t rely on Eclipse/Buildship, consider dropping it to keep the patch minimal.

ios/RocketChatRN.xcodeproj/project.pbxproj (1)

1868-1877: One Bugsnag “Upload source maps” phase is now effectively a no‑op

For the Rocket.ChatRN target you still have two “Upload source maps to Bugsnag” phases:

  • One with a full script using bugsnag-react-native-xcode.sh.
  • Another (7AAE9EB3…) whose shellScript is now just a blank newline.

The empty phase is harmless but does nothing and adds noise to the build. Consider either:

  • Removing that build phase entirely, or
  • Restoring a real script if it’s meant to perform work.

Also applies to: 1954-1964

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3532447 and 3498353.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (48)
  • __mocks__/react-native-mmkv-storage.js (0 hunks)
  • __mocks__/react-native-mmkv.js (1 hunks)
  • android/app/build.gradle (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (3 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/SecureStorage.java (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/SecureStoragePackage.java (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (2 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/storage/Constants.java (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboPackage.java (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/storage/NativeMMKVReaderSpec.java (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/storage/SecureKeystore.java (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/storage/Storage.java (1 hunks)
  • android/app/src/main/jni/MainApplicationModuleProvider.cpp (1 hunks)
  • app/containers/AudioPlayer/PlaybackSpeed.tsx (1 hunks)
  • app/index.tsx (2 hunks)
  • app/lib/helpers/ensureServersInDatabase.ts (1 hunks)
  • app/lib/methods/helpers/getSecureKey.ts (1 hunks)
  • app/lib/methods/helpers/sslPinning.ts (3 hunks)
  • app/lib/methods/migrateMMKVStorage.ts (1 hunks)
  • app/lib/methods/userPreferences.ts (1 hunks)
  • app/lib/native/NativeMMKVReader.android.ts (1 hunks)
  • app/lib/native/NativeMMKVReader.ts (1 hunks)
  • app/sagas/init.js (1 hunks)
  • app/views/AccessibilityAndAppearanceView/index.tsx (1 hunks)
  • app/views/MediaAutoDownloadView/index.tsx (1 hunks)
  • ios/AppDelegate.swift (1 hunks)
  • ios/MMKVMigration.h (1 hunks)
  • ios/MMKVMigration.mm (1 hunks)
  • ios/NotificationService/NotificationService-Bridging-Header.h (1 hunks)
  • ios/Podfile (2 hunks)
  • ios/RocketChatRN-Bridging-Header.h (1 hunks)
  • ios/RocketChatRN.xcodeproj/project.pbxproj (30 hunks)
  • ios/SSLPinning.mm (3 hunks)
  • ios/SSLPinning/SSLPinning.swift (2 hunks)
  • ios/SecureStorage.h (1 hunks)
  • ios/SecureStorage.m (1 hunks)
  • ios/Shared/RocketChat/ClientSSL.swift (1 hunks)
  • ios/Shared/RocketChat/MMKV.swift (1 hunks)
  • ios/Shared/RocketChat/MMKVBridge.h (1 hunks)
  • ios/Shared/RocketChat/MMKVBridge.mm (1 hunks)
  • ios/Shared/RocketChat/Storage.swift (1 hunks)
  • ios/Watch/WatchConnection.swift (1 hunks)
  • metro.config.js (1 hunks)
  • package.json (1 hunks)
  • patches/react-native-mmkv+3.3.3.patch (1 hunks)
  • patches/react-native-mmkv-storage+12.0.0.patch (0 hunks)
  • patches/react-native-webview+13.15.0.patch (3 hunks)
💤 Files with no reviewable changes (2)
  • mocks/react-native-mmkv-storage.js
  • patches/react-native-mmkv-storage+12.0.0.patch
🧰 Additional context used
🧬 Code graph analysis (23)
app/containers/AudioPlayer/PlaybackSpeed.tsx (1)
app/containers/AudioPlayer/constants.ts (1)
  • AVAILABLE_SPEEDS (1-1)
app/views/MediaAutoDownloadView/index.tsx (1)
app/lib/constants/mediaAutoDownload.ts (1)
  • MediaDownloadOption (1-1)
android/app/src/main/java/chat/rocket/reactnative/storage/SecureKeystore.java (2)
android/app/src/main/java/chat/rocket/reactnative/storage/Constants.java (1)
  • Constants (8-29)
android/app/src/main/java/chat/rocket/reactnative/storage/Storage.java (1)
  • Storage (15-35)
android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (2)
android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboPackage.java (1)
  • MMKVReaderTurboPackage (15-45)
android/app/src/main/java/chat/rocket/reactnative/SecureStoragePackage.java (1)
  • SecureStoragePackage (12-24)
app/lib/methods/helpers/getSecureKey.ts (2)
android/app/src/main/java/chat/rocket/reactnative/SecureStorage.java (1)
  • SecureStorage (24-162)
app/lib/methods/appGroup.ts (1)
  • appGroupPath (5-5)
app/index.tsx (1)
app/lib/methods/migrateMMKVStorage.ts (1)
  • migrateFromOldMMKV (7-65)
app/sagas/init.js (2)
app/lib/methods/userPreferencesMethods.ts (1)
  • getSortPreferences (6-8)
app/actions/sortPreferences.ts (1)
  • setAllPreferences (16-21)
app/lib/helpers/ensureServersInDatabase.ts (2)
app/lib/constants/keys.ts (1)
  • TOKEN_KEY (25-25)
app/sagas/init.js (1)
  • serversDB (39-39)
ios/Shared/RocketChat/MMKVBridge.h (1)
app/lib/methods/userPreferences.ts (1)
  • setString (32-38)
app/lib/methods/migrateMMKVStorage.ts (1)
__mocks__/react-native-mmkv.js (1)
  • MMKV (11-94)
ios/Watch/WatchConnection.swift (1)
ios/Shared/RocketChat/MMKV.swift (1)
  • build (4-20)
android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (1)
android/app/src/main/java/chat/rocket/reactnative/SecureStorage.java (1)
  • SecureStorage (24-162)
app/lib/methods/helpers/sslPinning.ts (1)
app/definitions/ICertificate.ts (1)
  • ICertificate (1-4)
android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboPackage.java (1)
android/app/src/main/java/chat/rocket/reactnative/storage/NativeMMKVReaderSpec.java (1)
  • NativeMMKVReaderSpec (9-30)
app/lib/methods/userPreferences.ts (4)
__mocks__/react-native-mmkv.js (1)
  • MMKV (11-94)
metro.config.js (1)
  • config (10-19)
app/lib/methods/appGroup.ts (1)
  • appGroupPath (5-5)
app/lib/methods/helpers/getSecureKey.ts (1)
  • getAppGroupPath (43-55)
ios/Shared/RocketChat/Storage.swift (1)
ios/Shared/RocketChat/MMKV.swift (1)
  • build (4-20)
ios/Shared/RocketChat/MMKV.swift (1)
ios/Shared/Extensions/FileManager+Extensions.swift (1)
  • groupDir (4-12)
ios/AppDelegate.swift (1)
ios/SSLPinning/SSLPinning.swift (1)
  • migrate (24-43)
__mocks__/react-native-mmkv.js (1)
metro.config.js (1)
  • config (10-19)
android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (2)
__mocks__/react-native-mmkv.js (1)
  • MMKV (11-94)
android/app/src/main/java/chat/rocket/reactnative/storage/NativeMMKVReaderSpec.java (1)
  • NativeMMKVReaderSpec (9-30)
ios/MMKVMigration.h (1)
ios/SSLPinning/SSLPinning.swift (1)
  • migrate (24-43)
android/app/src/main/java/chat/rocket/reactnative/SecureStorage.java (1)
app/lib/methods/helpers/getSecureKey.ts (2)
  • getSecureKey (22-31)
  • setSecureKey (33-41)
ios/SSLPinning/SSLPinning.swift (1)
ios/Shared/RocketChat/MMKV.swift (1)
  • build (4-20)
🪛 ast-grep (0.40.0)
android/app/src/main/java/chat/rocket/reactnative/SecureStorage.java

[warning] 75-75: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: KeyStore.getInstance(KEYSTORE_PROVIDER)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)


[warning] 83-83: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(TRANSFORMATION)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)


[warning] 104-104: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: KeyStore.getInstance(KEYSTORE_PROVIDER)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)


[warning] 109-112: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
KEYSTORE_PROVIDER
)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)


[warning] 128-128: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(TRANSFORMATION)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)


[warning] 75-75: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: KeyStore.getInstance(KEYSTORE_PROVIDER)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 83-83: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(TRANSFORMATION)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 104-104: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: KeyStore.getInstance(KEYSTORE_PROVIDER)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 109-112: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
KEYSTORE_PROVIDER
)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)


[warning] 128-128: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(TRANSFORMATION)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)

🪛 Clang (14.0.6)
ios/NotificationService/NotificationService-Bridging-Header.h

[error] 5-5: 'SecureStorage.h' file not found

(clang-diagnostic-error)

ios/Shared/RocketChat/MMKVBridge.h

[error] 8-8: 'Foundation/Foundation.h' file not found

(clang-diagnostic-error)

ios/MMKVMigration.h

[error] 8-8: 'Foundation/Foundation.h' file not found

(clang-diagnostic-error)

ios/SecureStorage.h

[error] 1-1: 'React/RCTBridgeModule.h' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (34)
app/lib/methods/helpers/getSecureKey.ts (1)

13-41: Confirm alias hex‑encoding is compatible with existing stored keys

You’re now normalizing aliases via stringToHex before calling SecureStorage.getSecureKey / setSecureKey. This is a good way to keep aliases filesystem/KeyStore‑friendly, but it changes the effective alias string seen by the native layer.

Please double‑check:

  • There is no existing data that was stored using raw aliases against SecureStorage (Android or iOS). If there is, you’ll need a migration step that looks up old aliases and re‑stores them under the hex‑encoded form.
  • All new call sites of SecureStorage go through this helper so alias handling stays consistent across the app and over time.

If everything using secure keys is new in this PR, this is fine; otherwise, a small migration helper may be necessary.

app/lib/methods/helpers/sslPinning.ts (3)

69-69: Review comment is incorrect.

The native SSLPinning.setCertificate() method is synchronous, not asynchronous. The implementation is declared as a regular function (@objc func setCertificate) and exported via RCT_EXTERN_METHOD (not RCT_EXTERN_ASYNC_METHOD), which indicates it returns immediately without a promise. The method performs file I/O and MMKV storage synchronously, so awaiting it at line 69 would be incorrect.

Likely an incorrect or invalid review comment.


22-35: Disregard the original review comment—persistCertificate should not be async.

The original concern was based on an incorrect assumption. Verification shows that UserPreferences.setMap() and UserPreferences.setString() both return void and are synchronous methods—they did not become async in the react-native-mmkv migration. Since persistCertificate only calls these synchronous methods and has no async operations, it correctly remains non-async.

The actual issue is the opposite: lines 68 and 87 are unnecessarily awaiting persistCertificate. These await statements should be removed since the function is synchronous.

Likely an incorrect or invalid review comment.


83-91: Verify external consumers handle the new async API signature.

The setCertificate method (line 83) is now async and exported as a public API (line 101). No internal call sites were found in the codebase, indicating this is likely consumed by external packages or applications. Ensure all external consumers of RCSSLPinning.setCertificate() are updated to properly await the returned promise.

Additionally, line 88 calls the native SSLPinning?.setCertificate() without awaiting it, consistent with the same concern at line 69—verify whether this should be awaited as well.

app/views/AccessibilityAndAppearanceView/index.tsx (1)

115-115: The guard on line 115 is justified, but there's a codebase-wide consistency issue that needs addressing.

The useUserPreferences hook explicitly returns [T | undefined, setter], which means the nullish coalescing on line 115 is a valid defensive coding pattern.

However, verification reveals an inconsistency: the same hook with identical default value ('TOAST') is used in Toast.tsx line 31 but directly compared without a guard on line 45 (if (alertDisplayType === 'DIALOG')). Additionally, in AccessibilityAndAppearanceView, boolean preferences from the hook (lines 27-28 without defaults, line 32 with default) are passed to Switch components without guards, despite the hook's type signature allowing undefined.

Either:

  1. Apply the guard pattern consistently across all uses of useUserPreferences to match line 115's defensive approach, or
  2. Verify whether the hook actually returns undefined in practice despite the permissive type signature, and update the types accordingly
app/lib/methods/userPreferences.ts (5)

8-15: LGTM: Storage singleton pattern is appropriate.

The lazy initialization pattern ensures the MMKV instance is created once and reused across the application.


32-38: LGTM: Setter and utility methods are well-implemented.

All methods include appropriate error handling with console logging, and the MMKV API is used correctly throughout.

Also applies to: 48-54, 65-71, 73-79, 90-120


195-206: LGTM: App group path helper is correctly implemented.

The synchronous access to NativeModules.AppGroup.path matches the pattern used in app/lib/methods/appGroup.ts and includes appropriate error handling.


123-175: MMKV type system behavior verified—no issues found.

The mock implementation confirms MMKV 3.0.0 guarantees type-safe behavior: each typed getter (getString, getBoolean, getNumber) returns undefined for values that don't match its type. The hook's sequential type-checking logic correctly leverages this guarantee, ensuring no type conflicts across the fallback chain.


177-193: Code pattern is appropriate — no changes needed.

The defensive type assertion with optional chaining on line 182 is justified. While Mode.MULTI_PROCESS exists in the enum, multi-process behavior depends on platform/native setup (e.g. Android supports multi-process MMKV; on iOS you must configure App Groups / shared container) and there have been historical codegen/runtime issues in some versions. The current pattern safely handles this variability across platforms and library versions.

app/containers/AudioPlayer/PlaybackSpeed.tsx (1)

15-18: Casting playbackSpeed for indexOf is a reasonable TS fix

Given useUserPreferences<number> and numeric AVAILABLE_SPEEDS, this cast just satisfies the type checker and does not alter runtime behavior; the cycling logic remains correct.

metro.config.js (1)

15-18: E2E mock resolution tied explicitly to RUNNING_E2E_TESTS='true'

The stricter check matches the e2e:start script and keeps .mock.ts resolution opt‑in for E2E only, which is helpful given the MMKV mock being disabled via patch-package.

ios/Podfile (1)

11-11: MMKV Pod entry looks correct; ensure CocoaPods integration stays green

Adding pod 'react-native-mmkv', path: '../node_modules/react-native-mmkv', modular_headers: true is consistent with a local RN pod setup alongside use_frameworks! :linkage => :static. Please confirm pod install (and CI iOS builds) succeed with this and that any old react-native-mmkv-storage pod entries are fully removed.

app/lib/native/NativeMMKVReader.ts (1)

1-22: NativeMMKVReader TS fallback interface looks solid

The interface cleanly describes the native module surface and the declare const MMKVReader: MMKVReaderModule | null pattern is appropriate as a type-only fallback with platform-specific implementations provided elsewhere. No changes needed here.

ios/Shared/RocketChat/Storage.swift (1)

9-15: MMKVBridge swap-in looks straightforward

Switching to MMKVBridge.build() here is a clean drop-in replacement as long as MMKVBridge still exposes privateKey(for:) with the previous semantics. No additional changes needed in this file.

ios/Watch/WatchConnection.swift (1)

6-8: Watch MMKVBridge usage aligns with main app; just ensure shared config

Using MMKVBridge.build() in the watch extension is consistent with the main app and should keep user/token data in sync via the app group path, provided build() uses the same group identifier for both targets. Worth double‑checking that the watch extension links the MMKVBridge module and shares the same app group value.

Also applies to: 43-47

android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (1)

29-32: Android MMKV initialization and TurboModule wiring look correct

Registering MMKVReaderTurboPackage() in getPackages() and calling MMKV.initialize(this) in onCreate() should be enough to make the new storage + reader available before JS runs. If you ever need to align the storage path with iOS (e.g., for debugging/mirroring), consider passing an explicit rootDir to MMKV.initialize, but it’s not required for correctness.

Also applies to: 37-43, 56-63

ios/MMKVMigration.h (1)

1-18: Minimal migration interface looks fine; check toolchain if Foundation import errors

The MMKVMigration interface is straightforward and conventional (+migrate on an NSObject subclass with <Foundation/Foundation.h> imported). If you’re actually seeing the “Foundation/Foundation.h file not found” error outside of static analysis, it’s almost certainly an SDK/header search path issue rather than a problem with this header’s contents.

ios/Shared/RocketChat/ClientSSL.swift (1)

3-28: MMKVBridge extension correctly preserves legacy ClientSSL read behavior

Updating the extension to target MMKVBridge while leaving clientSSL(for:)’s internals unchanged keeps the “read old layout (RC_CERTIFICATE_KEY/host)” logic intact, now routed through the bridge. That matches its usage in SSLPinning.migrate() and cleanly separates legacy reads from the new ssl_pinning_* key scheme.

ios/SSLPinning/SSLPinning.swift (1)

8-22: SSLPinning’s move to MMKVBridge and new key scheme is coherent

Using MMKVBridge.build() and rewriting certificates into ssl_pinning_certificate / ssl_pinning_password via setData/setString gives you a clear separation between:

  • reading legacy MMKV data through mmkv.clientSSL(for:) during migrate(), and
  • using getCertificate against the new ssl_pinning_* keys afterwards.

That migration path looks consistent, and the new read/write keys in setCertificate and getCertificate line up correctly.

Also applies to: 24-55

android/app/src/main/jni/MainApplicationModuleProvider.cpp (1)

26-28: LGTM!

The MMKVReader TurboModule registration follows the established pattern and correctly integrates with the Java TurboModule infrastructure.

app/sagas/init.js (1)

27-31: LGTM!

The migration check correctly guards against data inconsistencies by ensuring ensureServersInDatabase runs only after the workspace migration is completed. The saga pattern usage is correct.

android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (1)

62-71: LGTM!

The SecureStorage integration is correct. The code properly handles the case where getSecureKeyInternal returns null, and MMKV initialization accepts null passwords gracefully.

ios/RocketChatRN-Bridging-Header.h (1)

5-7: LGTM!

The bridging header imports correctly reference the new local implementations needed for MMKV migration, secure storage, and the MMKVBridge infrastructure.

ios/AppDelegate.swift (2)

20-22: LGTM!

The migration is correctly placed at the very beginning of application launch, before any other initialization. The comment clearly explains the critical ordering requirement.


32-33: LGTM!

The deferred Firebase and Bugsnag initialization ensures MMKV migration completes before any analytics or crash reporting starts. This is the correct ordering to maintain data integrity, even though it means migration crashes won't be reported.

ios/NotificationService/NotificationService-Bridging-Header.h (1)

5-6: Files exist, but build configuration requires manual verification in Xcode.

The header files referenced in the bridging header do exist:

  • SecureStorage.h at ios/SecureStorage.h
  • MMKVBridge.h at ios/Shared/RocketChat/MMKVBridge.h

The relative path for MMKVBridge is correct. However, SecureStorage.h is imported as a bare name, which means it depends on correct header search paths in the Xcode build configuration. Verify in Xcode that:

  1. NotificationService target has ios/ directory in its header search paths
  2. Both headers are in the target membership

The static analysis error may be a false positive if build paths are properly configured.

app/lib/helpers/ensureServersInDatabase.ts (1)

7-43: <!-- [scratchpad_start]
[task verify_server_record_fields_complete]
[observations]

  • ensureServersInDatabase called from app/sagas/init.js after workspace migration
  • Creates stub records with only id and name
  • selectServer.ts creates/updates records with version field
  • version field is used extensively: compareServerVersion calls, state.server.version in Redux
  • compareServerVersion used for feature detection throughout app
  • autoLock accessed in ScreenLockConfigView
  • lastLocalAuthenticatedSession accessed in localAuthentication.ts
  • All schema fields are optional (isOptional: true)
    [analysis]
  • ensureServersInDatabase intentionally creates minimal stubs during migration/recovery
  • selectServer populates version when connecting to server
  • Risk: if stub records are accessed before selectServer populates version, compareServerVersion could fail
  • review comment is valid - unclear if null version will cause runtime issues
  • Function appears to be for recovery/migration, but version field dependency unclear
    [pending]
  • Cannot definitively determine if null version is safe without running tests
  • compareServerVersion implementation not fully examined
  • No clear guarantee stub records won't be accessed with null version
    [conclusion]
  • Review comment raises valid concern about field initialization
  • Verification shows version field is critical and used extensively
  • Cannot confirm if current implementation is safe or if it's an oversight
  • Developer should verify behavior with null/undefined version in compareServerVersion

Based on my investigation, I have verified the concern raised in the review comment.

Key findings:

  1. Function Purpose: ensureServersInDatabase is a migration/recovery function called from app/sagas/init.js after workspace migration completes. It creates stub records for server URLs found in user preferences.

  2. Current Implementation: The function only initializes id (via sanitizedRaw) and name fields, leaving all other server record fields as null/undefined.

  3. Server Schema Fields: The Server model has 17 optional fields including version, autoLock, autoLockTime, lastLocalAuthenticatedSession, uniqueID, enterpriseModules, etc.

  4. Version Field Dependency: The version field is critical and extensively used throughout the app:

    • Used in compareServerVersion calls for feature detection
    • Accessed from Redux state in many components
    • Set by selectServer.ts when properly connecting to a server
  5. Pattern Comparison: In app/sagas/selectServer.ts, server record creation includes version (and optionally supportedVersions/supportedVersionsUpdatedAt), which differs from ensureServersInDatabase.

Risk Assessment: If stub records created by ensureServersInDatabase are accessed before being properly updated by selectServer, the missing version field could cause runtime issues in compareServerVersion calls.


Consider initializing the version field with a default value (e.g., empty string or a safe default version) when creating server records.

This ensures server records are consistently initialized and prevents potential null/undefined errors when version-dependent code accesses these stub records. Verify whether other fields like autoLock, uniqueID, or enterpriseModules also require defaults for safety.

android/app/src/main/java/chat/rocket/reactnative/SecureStoragePackage.java (1)

12-24: SecureStorage ReactPackage wiring looks correct

SecureStoragePackage exposes a single native module and no view managers, matching the intended usage pattern. Nothing blocking here.

app/lib/native/NativeMMKVReader.android.ts (1)

1-20: TurboModule spec and export for MMKVReader look good

The Spec interface matches the described native API, and TurboModuleRegistry.getEnforcing('MMKVReader') is an appropriate way to expose the module on Android.

ios/Shared/RocketChat/MMKV.swift (1)

3-20: MMKVBridge builder matches cross‑platform storage layout.

The build() helper cleanly derives the app group path, creates <groupDir>/mmkv, and instantiates MMKVBridge(id: "default", cryptKey: …, rootPath: …), which lines up with the TS side using new MMKV({ id: 'default' }) and the shared mmkv directory name. This should keep iOS storage aligned with the new MMKV layout without changing the existing token/private key accessors.

ios/SecureStorage.m (1)

5-5: I'll verify the Objective-C @implementation syntax claim:

@implementation syntax is invalid and will not compile

The implementation doesn't need to repeat the superclass name. While you can use a trailing colon followed by the parent class name in the @implementation section as was done in the @interface section, this is optional and typically not done.

Remove the superclass declaration from the @implementation line:

-@implementation SecureStorage : NSObject
+@implementation SecureStorage
patches/react-native-webview+13.15.0.patch (1)

128-152: Alias plumbing from JS → module → client looks consistent

The new static setCertificateAlias helpers on RNCWebViewModuleImpl, RNCWebViewModule (new/old arch), and the managers correctly forward the alias down to RNCWebViewClient.setCertificateAlias. This wiring should make the JS‑side API straightforward.

No issues from a wiring perspective here.

Also applies to: 172-206

ios/RocketChatRN.xcodeproj/project.pbxproj (1)

2542-2551: MMKV header paths and FORCE_POSIX defines look reasonable; just confirm they match Pod layout

You’ve:

  • Added FORCE_POSIX=1 to GCC_PREPROCESSOR_DEFINITIONS for the main app and NotificationService targets.
  • Updated HEADER_SEARCH_PATHS for RocketChatRN + Rocket.Chat to include $(SRCROOT)/../node_modules/react-native-mmkv/MMKV/Core/**.
  • Added simple HEADER_SEARCH_PATHS = ("$(inherited)", "$(SRCROOT)", ); for NotificationService.

All of this is consistent with an MMKV migration, assuming your Pod/headers for react-native-mmkv are indeed under node_modules/react-native-mmkv/MMKV/Core/** and that FORCE_POSIX is required by your MMKV integration.

I’d just double‑check:

  • That the MMKV Pod actually exposes headers at this path for all configurations.
  • That FORCE_POSIX is intentional and documented somewhere (so future maintainers know why it’s there).

Otherwise these changes look fine.

Also applies to: 2608-2617, 3127-3136, 3191-3200, 3023-3031, 3076-3084

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/lib/helpers/ensureServersInDatabase.ts (1)

7-44: Consider adding comprehensive error handling and logging.

The function performs multiple database operations without any error handling. While the database write transaction provides some safety, adding explicit error handling would improve:

  1. Debuggability: Log which step failed during migration
  2. User experience: Provide meaningful error context to calling code
  3. Monitoring: Track migration success rates

Consider wrapping critical sections in try-catch blocks with appropriate logging, especially since this is a migration helper that runs during app initialization.

Example structure:

const ensureServersInDatabase = async (): Promise<void> => {
	try {
		const prefix = `${TOKEN_KEY}-`;
		const keys = userPreferences.getAllKeys();
		// ... rest of logic
	} catch (error) {
		console.error('Failed to ensure servers in database:', error);
		// Optionally: track analytics, show user message, etc.
		throw error; // Re-throw to signal failure to caller
	}
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3498353 and 5653229.

📒 Files selected for processing (2)
  • app/lib/helpers/ensureServersInDatabase.ts (1 hunks)
  • app/sagas/init.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/sagas/init.js
🔇 Additional comments (1)
app/lib/helpers/ensureServersInDatabase.ts (1)

36-39: The review comment is incorrect.

The pattern of setting _raw with sanitizedRaw({ id: ... }, schema) and then directly assigning model fields like record.name is the correct and recommended WatermelonDB approach. The code at lines 36–39 follows this standard pattern correctly and requires no changes.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5653229 and 6312933.

📒 Files selected for processing (3)
  • android/app/build.gradle (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (2 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
🧰 Additional context used
🧬 Code graph analysis (1)
android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (1)
android/app/src/main/java/chat/rocket/reactnative/storage/NativeMMKVReaderSpec.java (1)
  • NativeMMKVReaderSpec (9-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (4)
android/app/build.gradle (1)

156-156: Excellent: already using stable androidx.security:security-crypto:1.1.0.

The dependency has been correctly updated to the stable release version instead of the alpha version flagged in the previous review. This is a security-sensitive library, so using the stable version is the right choice.

android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (3)

3-3: Import added correctly.

The java.io.File import that was flagged in the previous review is now present, resolving the compilation issue.


28-43: LGTM!

The getStoragePath method correctly retrieves directory paths and existence status with appropriate error handling.


45-71: LGTM!

The listMMKVFiles method includes proper defensive checks (directory existence, null array) and comprehensive error handling.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@OtavioStasiak OtavioStasiak deployed to approve_e2e_testing November 24, 2025 22:08 — with GitHub Actions Active
@OtavioStasiak OtavioStasiak deployed to official_ios_build November 24, 2025 22:11 — with GitHub Actions Active
@OtavioStasiak OtavioStasiak requested a deployment to official_android_build November 24, 2025 22:11 — with GitHub Actions Waiting
@OtavioStasiak OtavioStasiak deployed to experimental_ios_build November 24, 2025 22:11 — with GitHub Actions Active
@OtavioStasiak OtavioStasiak deployed to experimental_android_build November 24, 2025 22:11 — with GitHub Actions Active
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
package.json (1)

108-108: ✓ Version pinned correctly; past review concern resolved.

The exact version pin "3.3.3" (no caret) successfully addresses the prior review concern about semver-compatible upgrades bypassing the patch file. React Native 0.79.4 exceeds the minimum requirement of 0.74+ for react-native-mmkv V3, and the migration is well-scoped.

🧹 Nitpick comments (3)
ios/MMKVMigration.mm (2)

28-152: Validate migration root path and avoid completely silent failures

The overall control flow in +migrate looks solid (idempotent flag, app group lookup, app-group → Documents fallback, and the new groupURL nil guard). Two points to double‑check:

  • The mmkvPath you derive here is also used as rootPath when creating the new MMKVBridge for "default". If other code in the app uses a different root path for the runtime MMKV instance (e.g., a separate helper or default path), you could be writing migrated data to a location the app never reads. It’s worth confirming that the same rootPath resolution is used everywhere.
  • The top‑level @try/@catch swallows all NSExceptions without any logging or diagnostics. If something goes wrong (filesystem permission, MMKV init issue, etc.), the app will just silently skip migration and keep retrying on every launch. Even a minimal log/metric in debug or a single error log guarded for release would make this much easier to diagnose in the field.

154-193: Confirm that value-type coverage matches what the old storage actually used

readInstanceData:withPassword:atPath: only attempts stringForKey: and then dataForKey:; later, in +migrate, any NSNumber values you encounter are written back as strings. This is fine if react-native-mmkv-storage always stored app-visible values as strings or binary blobs, but if it ever persisted booleans/ints/floats as native numeric types:

  • Those keys might not be captured here (depending on how MMKVBridge exposes typed getters), or
  • They may come back as raw NSData and be written into the new store in a way that no longer matches how the JS side reads them (e.g., previously via numeric APIs).

Please confirm that this string/data‑only read and string (for NSNumber)/data writeback preserves behavior for all keys that matter. If the previous layer used typed numeric/bool APIs, you may want to extend the migration to explicitly handle those types so you don’t silently change how preferences or counters are represented.

android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (1)

89-89: MMKV.initialize is idempotent and safe for repeated calls — no issue, but consider a minor optimization.

MMKV.initialize() is generally safe to call multiple times with the same parameters, as it sets a static rootDir and invokes the native initializer. Since readAndDecryptMMKV is a one-time legacy migration utility and no call sites exist elsewhere in the codebase, repeated initialization is not a practical concern.

However, for cleaner code, consider moving MMKV.initialize(reactContext) outside this method to avoid redundant initialization—initialize it once during app startup (e.g., in Application.onCreate or a dedicated initialization method) rather than per-invocation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6312933 and 7b0ca79.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (1 hunks)
  • app/lib/helpers/ensureServersInDatabase.ts (1 hunks)
  • ios/MMKVMigration.mm (1 hunks)
  • ios/RocketChatRN.xcodeproj/project.pbxproj (24 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (2)
android/app/src/main/java/chat/rocket/reactnative/storage/NativeMMKVReaderSpec.java (1)
  • NativeMMKVReaderSpec (9-30)
app/lib/methods/helpers/getSecureKey.ts (1)
  • getSecureKey (22-31)
app/lib/helpers/ensureServersInDatabase.ts (2)
app/lib/constants/keys.ts (1)
  • TOKEN_KEY (25-25)
app/sagas/init.js (1)
  • serversDB (39-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (10)
ios/MMKVMigration.mm (1)

13-24: toHex helper looks correct and nil-safe

The UTF‑8 → hex conversion loop and the early return for nil are both sound for the aliasing use case; no changes needed here.

app/lib/helpers/ensureServersInDatabase.ts (1)

33-48: Error handling and transaction flow look good.

The past critical issue regarding migration flag placement and error handling has been properly addressed. The flag is now set inside the try block after the database write succeeds, and errors are logged and rethrown for proper propagation to the caller.

ios/RocketChatRN.xcodeproj/project.pbxproj (2)

1963-1963: Bugsnag source map upload script has been emptied.

The "Upload source maps to Bugsnag" build phase script was replaced with an empty script. This will prevent source maps from being uploaded, degrading crash report symbolication in production.

If this is intentional during development, consider restoring it before release. If the migration requires a different approach for Bugsnag integration, ensure source map uploads are configured accordingly.


2541-2550: Build settings correctly configured for react-native-mmkv.

The FORCE_POSIX=1 preprocessor definition and updated HEADER_SEARCH_PATHS pointing to react-native-mmkv/MMKV/Core/** are correctly configured for the MMKV migration. These changes are consistently applied across Debug/Release configurations for RocketChatRN, Rocket.Chat, and NotificationService targets.

android/app/src/main/java/chat/rocket/reactnative/storage/MMKVReaderTurboModule.java (6)

1-21: LGTM! Well-structured module initialization.

The imports, class structure, and constructor follow TurboModule best practices correctly. The missing File import issue from the previous review has been properly addressed.


28-43: LGTM! Clean diagnostic helper.

The method provides useful path information for debugging migration issues, with appropriate error handling.


45-71: LGTM! Robust file listing implementation.

The defensive checks for directory existence and null files array prevent potential NPEs. The detailed file information will be helpful for migration diagnostics.


116-138: Type detection improvements look good; confirm edge case handling is sufficient.

The updates address the previous review concerns well:

✅ Line 131 adds containsKey check before boolean decode (prevents false-positive boolean detection)
✅ Line 136 logs decode errors instead of silently swallowing them

However, the fundamental limitations remain:

  • Line 125-126: If a key actually stores Integer.MIN_VALUE, it will be misdetected and skipped
  • Line 132: A boolean key storing false will return false (correct), but if the key is missing entirely, it would also return false—though the containsKey guard on line 131 mitigates this

The documentation (lines 76-80) acknowledges these are acceptable trade-offs for one-time legacy migration. Please confirm:

  1. The migrated data is not expected to contain Integer.MIN_VALUE as a stored integer
  2. If type misdetection occurs for edge cases, the migration can be re-run or manually corrected
  3. This is truly read-only and one-time (not used in production data paths)

149-160: LGTM! Hex conversion correctly fixed.

The formatting now correctly handles signed bytes with (b & 0xff) masking and %02x padding, addressing the previous review issue. The implementation matches the TypeScript equivalent in getSecureKey.ts.


92-98: No explicit cleanup is needed—code follows correct MMKV and SecureKeystore lifecycle patterns.

SecureKeystore is a utility class that wraps Android Keystore System and manages encryption keys via SharedPreferences. Neither Android Keystore nor SharedPreferences require explicit disposal.

MMKV best practice is to initialize once in Application.onCreate and keep instances open for the app lifetime. Don't call close() unless you are certain that no code will access that MMKV ID again, as close() marks the instance unusable. The code correctly avoids calling close() on the MMKV instance, which aligns with library guidance for typical app usage.

@OtavioStasiak OtavioStasiak requested a deployment to upload_experimental_android November 24, 2025 22:39 — with GitHub Actions Waiting
@github-actions
Copy link

Android Build Available

Rocket.Chat Experimental 4.67.0.107723

Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNQXUmbZHIh67oKi1CJ7DgTd7k268gpUYE7g_2GVgNKwpdtbIMutJS0thjZzYDoOMhgmqOuL2TJqlwu94ORI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants