diff --git a/native/android/app/CMakeLists.txt b/native/android/app/CMakeLists.txt --- a/native/android/app/CMakeLists.txt +++ b/native/android/app/CMakeLists.txt @@ -119,6 +119,7 @@ "./src/cpp/AESCrypto.cpp" "./src/cpp/CommServicesAuthMetadataEmitter.cpp" "./src/cpp/CommMMKV.cpp" + "./src/cpp/CommMMKVJNIHelper.cpp" "./src/cpp/NotificationsInboundKeysProvider.cpp" ) diff --git a/native/android/app/src/cpp/CommMMKVJNIHelper.cpp b/native/android/app/src/cpp/CommMMKVJNIHelper.cpp new file mode 100644 --- /dev/null +++ b/native/android/app/src/cpp/CommMMKVJNIHelper.cpp @@ -0,0 +1,15 @@ +#include +#include + +namespace comm { +std::string CommMMKVJNIHelper::notifsStorageUnreadThickThreadsKey( + facebook::jni::alias_ref jThis) { + return CommMMKV::notifsStorageUnreadThickThreadsKey; +} + +void CommMMKVJNIHelper::registerNatives() { + javaClassStatic()->registerNatives({makeNativeMethod( + "notifsStorageUnreadThickThreadsKey", + CommMMKVJNIHelper::notifsStorageUnreadThickThreadsKey)}); +} +} // namespace comm diff --git a/native/android/app/src/cpp/jsiInstaller.cpp b/native/android/app/src/cpp/jsiInstaller.cpp --- a/native/android/app/src/cpp/jsiInstaller.cpp +++ b/native/android/app/src/cpp/jsiInstaller.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace jni = facebook::jni; @@ -76,5 +77,6 @@ comm::DatabaseInitializerJNIHelper::registerNatives(); comm::NotificationsCryptoModuleJNIHelper::registerNatives(); comm::StaffUtilsJNIHelper::registerNatives(); + comm::CommMMKVJNIHelper::registerNatives(); }); } diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java --- a/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java +++ b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java @@ -20,6 +20,8 @@ private static String mmkvEncryptionKey; private static String mmkvIdentifier; + public static native String notifsStorageUnreadThickThreadsKey(); + private static MMKV getMMKVInstance(String mmkvID, String encryptionKey) { MMKV mmkv = MMKV.mmkvWithID(mmkvID, MMKV.MULTI_PROCESS_MODE, encryptionKey); if (mmkv == null) { diff --git a/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java b/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java --- a/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java +++ b/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java @@ -68,8 +68,6 @@ private static final String MMKV_KEY_SEPARATOR = "."; private static final String MMKV_KEYSERVER_PREFIX = "KEYSERVER"; private static final String MMKV_UNREAD_COUNT_SUFFIX = "UNREAD_COUNT"; - private static final String MMKV_UNREAD_THICK_THREADS = - "NOTIFS.UNREAD_THICK_THREADS"; private Bitmap displayableNotificationLargeIcon; private NotificationManager notificationManager; private LocalBroadcastManager localBroadcastManager; @@ -307,12 +305,14 @@ message.getData().get(THREAD_ID_KEY) != null && message.getData().get(RESCIND_KEY) != null) { CommMMKV.removeElementFromStringSet( - MMKV_UNREAD_THICK_THREADS, message.getData().get(THREAD_ID_KEY)); + CommMMKV.notifsStorageUnreadThickThreadsKey(), + message.getData().get(THREAD_ID_KEY)); } else if ( message.getData().get(SENDER_DEVICE_ID_KEY) != null && message.getData().get(THREAD_ID_KEY) != null) { CommMMKV.addElementToStringSet( - MMKV_UNREAD_THICK_THREADS, message.getData().get(THREAD_ID_KEY)); + CommMMKV.notifsStorageUnreadThickThreadsKey(), + message.getData().get(THREAD_ID_KEY)); } int totalUnreadCount = 0; @@ -332,7 +332,9 @@ totalUnreadCount += unreadCount; } - totalUnreadCount += CommMMKV.getStringSet(MMKV_UNREAD_THICK_THREADS).length; + totalUnreadCount += + CommMMKV.getStringSet(CommMMKV.notifsStorageUnreadThickThreadsKey()) + .length; if (totalUnreadCount > 0) { ShortcutBadger.applyCount(this, totalUnreadCount); diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -128,6 +128,11 @@ virtual jsi::Value getKeyserverDataFromNotifStorage( jsi::Runtime &rt, jsi::Array keyserverIDs) override; + virtual jsi::Value updateUnreadThickThreadsInNotifsStorage( + jsi::Runtime &rt, + jsi::Array unreadThickThreadIDs) override; + virtual jsi::Value + getUnreadThickThreadIDsFromNotifsStorage(jsi::Runtime &rt) override; virtual jsi::Value initializeContentOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -1471,6 +1471,75 @@ }); } +jsi::Value CommCoreModule::updateUnreadThickThreadsInNotifsStorage( + jsi::Runtime &rt, + jsi::Array unreadThickThreadIDs) { + std::vector unreadThickThreadIDsCpp{}; + for (auto idx = 0; idx < unreadThickThreadIDs.size(rt); idx++) { + std::string thickThreadID = + unreadThickThreadIDs.getValueAtIndex(rt, idx).asString(rt).utf8(rt); + unreadThickThreadIDsCpp.push_back(thickThreadID); + } + + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + std::string error; + try { + CommMMKV::setStringSet( + CommMMKV::notifsStorageUnreadThickThreadsKey, + unreadThickThreadIDsCpp); + } catch (const std::exception &e) { + error = e.what(); + } + + this->jsInvoker_->invokeAsync([=, &innerRt]() { + if (error.size()) { + promise->reject(error); + return; + } + promise->resolve(jsi::Value::undefined()); + }); + }); +} + +jsi::Value +CommCoreModule::getUnreadThickThreadIDsFromNotifsStorage(jsi::Runtime &rt) { + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + std::string error; + std::vector unreadThickThreadIDs{}; + try { + unreadThickThreadIDs = CommMMKV::getStringSet( + CommMMKV::notifsStorageUnreadThickThreadsKey); + } catch (const std::exception &e) { + error = e.what(); + } + + auto unreadThreadThickThreadIDsPtr = + std::make_shared>( + std::move(unreadThickThreadIDs)); + + this->jsInvoker_->invokeAsync([=, &innerRt]() { + if (error.size()) { + promise->reject(error); + return; + } + + jsi::Array jsiUnreadThickThreadIDs = + jsi::Array(innerRt, unreadThreadThickThreadIDsPtr->size()); + size_t writeIdx = 0; + + for (const auto &thickThreadID : *unreadThreadThickThreadIDsPtr) { + jsi::String jsiThickThreadID = + jsi::String::createFromUtf8(innerRt, thickThreadID); + jsiUnreadThickThreadIDs.setValueAtIndex( + innerRt, writeIdx++, jsiThickThreadID); + } + promise->resolve(std::move(jsiUnreadThickThreadIDs)); + }); + }); +} + jsi::Value CommCoreModule::initializeContentOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, diff --git a/native/cpp/CommonCpp/Tools/CommMMKV.h b/native/cpp/CommonCpp/Tools/CommMMKV.h --- a/native/cpp/CommonCpp/Tools/CommMMKV.h +++ b/native/cpp/CommonCpp/Tools/CommMMKV.h @@ -34,6 +34,9 @@ static bool setStringSet(std::string key, const std::vector &elements); + inline static const std::string notifsStorageUnreadThickThreadsKey = + "NOTIFS.UNREAD_THICK_THREADS"; + class InitFromNSEForbiddenError : public std::runtime_error { public: using std::runtime_error::runtime_error; diff --git a/native/cpp/CommonCpp/Tools/CommMMKVJNIHelper.h b/native/cpp/CommonCpp/Tools/CommMMKVJNIHelper.h new file mode 100644 --- /dev/null +++ b/native/cpp/CommonCpp/Tools/CommMMKVJNIHelper.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace comm { +class CommMMKVJNIHelper : public facebook::jni::JavaClass { +public: + static auto constexpr kJavaDescriptor = "Lapp/comm/android/fbjni/CommMMKV;"; + static std::string notifsStorageUnreadThickThreadsKey( + facebook::jni::alias_ref jThis); + static void registerNatives(); +}; +} // namespace comm diff --git a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp --- a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp @@ -84,6 +84,12 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getKeyserverDataFromNotifStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getKeyserverDataFromNotifStorage(rt, args[0].asObject(rt).asArray(rt)); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateUnreadThickThreadsInNotifsStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->updateUnreadThickThreadsInNotifsStorage(rt, args[0].asObject(rt).asArray(rt)); +} +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getUnreadThickThreadIDsFromNotifsStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->getUnreadThickThreadIDsFromNotifsStorage(rt); +} static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeContentOutboundSession(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->initializeContentOutboundSession(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt), args[3].isNull() || args[3].isUndefined() ? std::nullopt : std::make_optional(args[3].asString(rt)), args[4].asString(rt)); } @@ -264,6 +270,8 @@ methodMap_["updateKeyserverDataInNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateKeyserverDataInNotifStorage}; methodMap_["removeKeyserverDataFromNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeKeyserverDataFromNotifStorage}; methodMap_["getKeyserverDataFromNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getKeyserverDataFromNotifStorage}; + methodMap_["updateUnreadThickThreadsInNotifsStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateUnreadThickThreadsInNotifsStorage}; + methodMap_["getUnreadThickThreadIDsFromNotifsStorage"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getUnreadThickThreadIDsFromNotifsStorage}; methodMap_["initializeContentOutboundSession"] = MethodMetadata {5, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeContentOutboundSession}; methodMap_["initializeContentInboundSession"] = MethodMetadata {5, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeContentInboundSession}; methodMap_["isContentSessionInitialized"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isContentSessionInitialized}; diff --git a/native/cpp/CommonCpp/_generated/commJSI.h b/native/cpp/CommonCpp/_generated/commJSI.h --- a/native/cpp/CommonCpp/_generated/commJSI.h +++ b/native/cpp/CommonCpp/_generated/commJSI.h @@ -43,6 +43,8 @@ virtual jsi::Value updateKeyserverDataInNotifStorage(jsi::Runtime &rt, jsi::Array keyserversData) = 0; virtual jsi::Value removeKeyserverDataFromNotifStorage(jsi::Runtime &rt, jsi::Array keyserverIDsToDelete) = 0; virtual jsi::Value getKeyserverDataFromNotifStorage(jsi::Runtime &rt, jsi::Array keyserverIDs) = 0; + virtual jsi::Value updateUnreadThickThreadsInNotifsStorage(jsi::Runtime &rt, jsi::Array unreadThickThreadIDs) = 0; + virtual jsi::Value getUnreadThickThreadIDsFromNotifsStorage(jsi::Runtime &rt) = 0; virtual jsi::Value initializeContentOutboundSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) = 0; virtual jsi::Value initializeContentInboundSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::Object encryptedContent, jsi::String deviceID, double sessionVersion, bool overwrite) = 0; virtual jsi::Value isContentSessionInitialized(jsi::Runtime &rt, jsi::String deviceID) = 0; @@ -298,6 +300,22 @@ return bridging::callFromJs( rt, &T::getKeyserverDataFromNotifStorage, jsInvoker_, instance_, std::move(keyserverIDs)); } + jsi::Value updateUnreadThickThreadsInNotifsStorage(jsi::Runtime &rt, jsi::Array unreadThickThreadIDs) override { + static_assert( + bridging::getParameterCount(&T::updateUnreadThickThreadsInNotifsStorage) == 2, + "Expected updateUnreadThickThreadsInNotifsStorage(...) to have 2 parameters"); + + return bridging::callFromJs( + rt, &T::updateUnreadThickThreadsInNotifsStorage, jsInvoker_, instance_, std::move(unreadThickThreadIDs)); + } + jsi::Value getUnreadThickThreadIDsFromNotifsStorage(jsi::Runtime &rt) override { + static_assert( + bridging::getParameterCount(&T::getUnreadThickThreadIDsFromNotifsStorage) == 1, + "Expected getUnreadThickThreadIDsFromNotifsStorage(...) to have 1 parameters"); + + return bridging::callFromJs( + rt, &T::getUnreadThickThreadIDsFromNotifsStorage, jsInvoker_, instance_); + } jsi::Value initializeContentOutboundSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) override { static_assert( bridging::getParameterCount(&T::initializeContentOutboundSession) == 6, diff --git a/native/ios/NotificationService/NotificationService.mm b/native/ios/NotificationService/NotificationService.mm --- a/native/ios/NotificationService/NotificationService.mm +++ b/native/ios/NotificationService/NotificationService.mm @@ -30,7 +30,6 @@ const std::string mmkvKeySeparator = "."; const std::string mmkvKeyserverPrefix = "KEYSERVER"; const std::string mmkvUnreadCountSuffix = "UNREAD_COUNT"; -const std::string unreadThickThreads = "NOTIFS.UNREAD_THICK_THREADS"; // The context for this constant can be found here: // https://linear.app/comm/issue/ENG-3074#comment-bd2f5e28 @@ -532,12 +531,12 @@ if (content.userInfo[senderDeviceIDKey] && content.userInfo[threadIDKey] && [self isRescind:content.userInfo]) { comm::CommMMKV::removeElementFromStringSet( - unreadThickThreads, + CommMMKV::notifsStorageUnreadThickThreadsKey, std::string([content.userInfo[threadIDKey] UTF8String])); } else if ( content.userInfo[senderDeviceIDKey] && content.userInfo[threadIDKey]) { comm::CommMMKV::addElementToStringSet( - unreadThickThreads, + CommMMKV::notifsStorageUnreadThickThreadsKey, std::string([content.userInfo[threadIDKey] UTF8String])); } @@ -563,7 +562,9 @@ } // calculate unread counts from thick threads - totalUnreadCount += comm::CommMMKV::getStringSet(unreadThickThreads).size(); + totalUnreadCount += + comm::CommMMKV::getStringSet(CommMMKV::notifsStorageUnreadThickThreadsKey) + .size(); content.badge = @(totalUnreadCount); } diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -83,6 +83,12 @@ +getKeyserverDataFromNotifStorage: ( keyserverIDs: $ReadOnlyArray, ) => Promise<$ReadOnlyArray<{ +id: string, +unreadCount: number }>>; + +updateUnreadThickThreadsInNotifsStorage: ( + unreadThickThreadIDs: $ReadOnlyArray, + ) => Promise; + +getUnreadThickThreadIDsFromNotifsStorage: () => Promise< + $ReadOnlyArray, + >; +initializeContentOutboundSession: ( identityKeys: string, prekey: string,