Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33183152
D11090.1768533187.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
17 KB
Referenced Files
None
Subscribers
None
D11090.1768533187.diff
View Options
diff --git a/lib/ops/keyserver-store-ops.js b/lib/ops/keyserver-store-ops.js
--- a/lib/ops/keyserver-store-ops.js
+++ b/lib/ops/keyserver-store-ops.js
@@ -130,7 +130,23 @@
},
};
+function getKeyserversToRemoveFromNotifsStore(
+ ops: $ReadOnlyArray<KeyserverStoreOperation>,
+): $ReadOnlyArray<string> {
+ const removeKeyserversOperations: Array<RemoveKeyserversOperation> = [];
+ for (const op of ops) {
+ if (op.type === 'remove_keyservers') {
+ removeKeyserversOperations.push(op);
+ }
+ }
+
+ return removeKeyserversOperations
+ .map(operation => operation.payload.ids)
+ .flat();
+}
+
export {
keyserverStoreOpsHandlers,
convertKeyserverInfoToClientDBKeyserverInfo,
+ getKeyserversToRemoveFromNotifsStore,
};
diff --git a/lib/selectors/thread-selectors.js b/lib/selectors/thread-selectors.js
--- a/lib/selectors/thread-selectors.js
+++ b/lib/selectors/thread-selectors.js
@@ -3,6 +3,7 @@
import _compact from 'lodash/fp/compact.js';
import _filter from 'lodash/fp/filter.js';
import _flow from 'lodash/fp/flow.js';
+import _groupBy from 'lodash/fp/groupBy.js';
import _map from 'lodash/fp/map.js';
import _mapValues from 'lodash/fp/mapValues.js';
import _orderBy from 'lodash/fp/orderBy.js';
@@ -18,6 +19,7 @@
} from './calendar-filter-selectors.js';
import { relativeMemberInfoSelectorForMembersOfThread } from './user-selectors.js';
import genesis from '../facts/genesis.js';
+import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js';
import {
getAvatarForThread,
getRandomDefaultEmojiAvatar,
@@ -288,6 +290,31 @@
).length,
);
+const extendedUnreadCount: (state: BaseAppState<>) => {
+ +[keyserverID: string]: number,
+} = createSelector(
+ (state: BaseAppState<>) => state.threadStore.threadInfos,
+ (threadInfos: RawThreadInfos): { +[keyserverID: string]: number } => {
+ const keyserverToThreads = _groupBy(threadInfo =>
+ extractKeyserverIDFromID(threadInfo.id),
+ )(
+ values(threadInfos).filter(threadInfo =>
+ threadInHomeChatList(threadInfo),
+ ),
+ );
+
+ const keyserverUnreadCountPairs = Object.entries(keyserverToThreads).map(
+ ([keyserverID, keyserverThreadInfos]) => [
+ keyserverID,
+ keyserverThreadInfos.filter(threadInfo => threadInfo.currentUser.unread)
+ .length,
+ ],
+ );
+
+ return Object.fromEntries(keyserverUnreadCountPairs);
+ },
+);
+
const unreadBackgroundCount: (state: BaseAppState<>) => number = createSelector(
(state: BaseAppState<>) => state.threadStore.threadInfos,
(threadInfos: RawThreadInfos): number =>
@@ -547,6 +574,7 @@
childThreadInfos,
containedThreadInfos,
unreadCount,
+ extendedUnreadCount,
unreadBackgroundCount,
unreadCountSelectorForCommunity,
otherUsersButNoOtherAdmins,
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
@@ -18,6 +18,7 @@
import app.comm.android.ExpoUtils;
import app.comm.android.MainActivity;
import app.comm.android.R;
+import app.comm.android.fbjni.CommMMKV;
import app.comm.android.fbjni.CommSecureStore;
import app.comm.android.fbjni.GlobalDBSingleton;
import app.comm.android.fbjni.MessageOperationsUtilities;
@@ -45,8 +46,11 @@
private static final String ENCRYPTION_FAILED_KEY = "encryptionFailed";
private static final String GROUP_NOTIF_IDS_KEY = "groupNotifIDs";
private static final String COLLAPSE_ID_KEY = "collapseKey";
+ private static final String KEYSERVER_ID_KEY = "keyserverID";
private static final String CHANNEL_ID = "default";
private static final long[] VIBRATION_SPEC = {500, 500};
+ private static final String MMKV_KEYSERVER_PREFIX = "KEYSERVER.";
+ private static final String MMKV_UNREAD_COUNT_SUFFIX = ".UNREAD_COUNT";
private Bitmap displayableNotificationLargeIcon;
private NotificationManager notificationManager;
private LocalBroadcastManager localBroadcastManager;
@@ -108,18 +112,10 @@
handleNotificationRescind(message);
}
- String badge = message.getData().get(BADGE_KEY);
- if (badge != null) {
- try {
- int badgeCount = Integer.parseInt(badge);
- if (badgeCount > 0) {
- ShortcutBadger.applyCount(this, badgeCount);
- } else {
- ShortcutBadger.removeCount(this);
- }
- } catch (NumberFormatException e) {
- Log.w("COMM", "Invalid badge count", e);
- }
+ try {
+ handleUnreadCountUpdate(message);
+ } catch (Exception e) {
+ Log.w("COMM", "Unread count update failure.", e);
}
String badgeOnly = message.getData().get(BADGE_ONLY_KEY);
@@ -214,6 +210,55 @@
}
}
+ private void handleUnreadCountUpdate(RemoteMessage message) {
+ String badge = message.getData().get(BADGE_KEY);
+ if (badge == null) {
+ return;
+ }
+
+ if (message.getData().get(KEYSERVER_ID_KEY) == null) {
+ throw new RuntimeException("Received badge update without keyserver ID.");
+ }
+ String senderKeyserverID = message.getData().get(KEYSERVER_ID_KEY);
+ String senderKeyserverUnreadCountKey =
+ MMKV_KEYSERVER_PREFIX + senderKeyserverID + MMKV_UNREAD_COUNT_SUFFIX;
+
+ int senderKeyserverUnreadCount;
+ try {
+ senderKeyserverUnreadCount = Integer.parseInt(badge);
+ } catch (NumberFormatException e) {
+ Log.w("COMM", "Invalid badge count", e);
+ return;
+ }
+ CommMMKV.setInt(senderKeyserverUnreadCountKey, senderKeyserverUnreadCount);
+
+ int totalUnreadCount = senderKeyserverUnreadCount;
+ String[] allKeys = CommMMKV.getAllKeys();
+ for (String key : allKeys) {
+ if (key.equals(senderKeyserverUnreadCountKey)) {
+ continue;
+ }
+
+ if (!key.startsWith(MMKV_KEYSERVER_PREFIX) ||
+ !key.endsWith(MMKV_UNREAD_COUNT_SUFFIX)) {
+ continue;
+ }
+
+ Integer unreadCount = CommMMKV.getInt(key, -1);
+ if (unreadCount == null) {
+ continue;
+ }
+
+ totalUnreadCount += unreadCount;
+ }
+
+ if (totalUnreadCount > 0) {
+ ShortcutBadger.applyCount(this, totalUnreadCount);
+ } else {
+ ShortcutBadger.removeCount(this);
+ }
+ }
+
private void addToThreadGroupAndDisplay(
String notificationID,
NotificationCompat.Builder notificationBuilder,
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
@@ -1,4 +1,5 @@
#import "NotificationService.h"
+#import "CommMMKV.h"
#import "Logger.h"
#import "NotificationsCryptoModule.h"
#import "StaffUtils.h"
@@ -10,6 +11,7 @@
NSString *const encryptedPayloadKey = @"encryptedPayload";
NSString *const encryptionFailureKey = @"encryptionFailure";
NSString *const collapseIDKey = @"collapseID";
+NSString *const keyserverIDKey = @"keyserverID";
const std::string callingProcessName = "NSE";
// The context for this constant can be found here:
// https://linear.app/comm/issue/ENG-3074#comment-bd2f5e28
@@ -140,7 +142,30 @@
addObject:[NSString stringWithUTF8String:persistErrorMessage.c_str()]];
}
- // Step 3: (optional) rescind read notifications
+ // Step 3: Cumulative unread count calculation
+ if (content.badge) {
+ std::string unreadCountCalculationError;
+ try {
+ @try {
+ [self calculateTotalUnreadCountInPlace:content];
+ } @catch (NSException *e) {
+ unreadCountCalculationError =
+ "Obj-C exception: " + std::string([e.name UTF8String]) +
+ " during unread count calculation.";
+ }
+ } catch (const std::exception &e) {
+ unreadCountCalculationError = "C++ exception: " + std::string(e.what()) +
+ " during unread count calculation.";
+ }
+
+ if (unreadCountCalculationError.size()) {
+ [errorMessages
+ addObject:[NSString stringWithUTF8String:unreadCountCalculationError
+ .c_str()]];
+ }
+ }
+
+ // Step 4: (optional) rescind read notifications
// Message payload persistence is a higher priority task, so it has
// to happen prior to potential notification center clearing.
@@ -172,7 +197,7 @@
publicUserContent = [[UNNotificationContent alloc] init];
}
- // Step 4: (optional) execute notification coalescing
+ // Step 5: (optional) execute notification coalescing
if ([self isCollapsible:content.userInfo]) {
std::string coalescingErrorMessage;
try {
@@ -202,7 +227,7 @@
}
}
- // Step 5: (optional) create empty notification that
+ // Step 6: (optional) create empty notification that
// only provides badge count.
if ([self needsSilentBadgeUpdate:content.userInfo]) {
UNMutableNotificationContent *badgeOnlyContent =
@@ -211,7 +236,7 @@
publicUserContent = badgeOnlyContent;
}
- // Step 5: notify main app that there is data
+ // Step 7: notify main app that there is data
// to transfer to SQLite and redux.
[self sendNewMessageInfosNotification];
@@ -415,6 +440,49 @@
[payload[backgroundNotificationTypeKey] isEqualToString:@"CLEAR"];
}
+- (void)calculateTotalUnreadCountInPlace:
+ (UNMutableNotificationContent *)content {
+ if (!content.userInfo[keyserverIDKey]) {
+ throw std::runtime_error("Received badge update without keyserver ID.");
+ }
+ std::string senderKeyserverID =
+ std::string([content.userInfo[keyserverIDKey] UTF8String]);
+
+ static const std::string keyserverPrefix = "KEYSERVER.";
+ static const std::string unreadCountSuffix = ".UNREAD_COUNT";
+ std::string senderKeyserverUnreadCountKey =
+ keyserverPrefix + senderKeyserverID + unreadCountSuffix;
+
+ int senderKeyserverUnreadCount = [content.badge intValue];
+ comm::CommMMKV::setInt(
+ senderKeyserverUnreadCountKey, senderKeyserverUnreadCount);
+
+ int totalUnreadCount = senderKeyserverUnreadCount;
+ std::vector<std::string> allKeys = comm::CommMMKV::getAllKeys();
+ for (const auto &key : allKeys) {
+ if (key == senderKeyserverUnreadCountKey) {
+ continue;
+ }
+
+ if (key.size() < keyserverPrefix.size() + unreadCountSuffix.size() ||
+ key.compare(0, keyserverPrefix.size(), keyserverPrefix) ||
+ key.compare(
+ key.size() - unreadCountSuffix.size(),
+ unreadCountSuffix.size(),
+ unreadCountSuffix)) {
+ continue;
+ }
+
+ std::optional<int> unreadCount = comm::CommMMKV::getInt(key, -1);
+ if (!unreadCount.has_value()) {
+ continue;
+ }
+ totalUnreadCount += unreadCount.value();
+ }
+
+ content.badge = @(totalUnreadCount);
+}
+
- (BOOL)needsSilentBadgeUpdate:(NSDictionary *)payload {
// TODO: refactor this check by introducing
// badgeOnly property in iOS notification payload
diff --git a/native/push/push-handler.react.js b/native/push/push-handler.react.js
--- a/native/push/push-handler.react.js
+++ b/native/push/push-handler.react.js
@@ -1,7 +1,6 @@
// @flow
import * as Haptics from 'expo-haptics';
-import invariant from 'invariant';
import * as React from 'react';
import { LogBox, Platform } from 'react-native';
import { Notification as InAppNotification } from 'react-native-in-app-message';
@@ -17,20 +16,18 @@
} from 'lib/actions/device-actions.js';
import { saveMessagesActionType } from 'lib/actions/message-actions.js';
import {
- connectionSelector,
deviceTokensSelector,
updatesCurrentAsOfSelector,
} from 'lib/selectors/keyserver-selectors.js';
import {
threadInfoSelector,
- unreadCount,
+ extendedUnreadCount,
} from 'lib/selectors/thread-selectors.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
import { mergePrefixIntoBody } from 'lib/shared/notif-utils.js';
import type { RawMessageInfo } from 'lib/types/message-types.js';
import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
import type { Dispatch } from 'lib/types/redux-types.js';
-import { type ConnectionInfo } from 'lib/types/socket-types.js';
import type { GlobalTheme } from 'lib/types/theme-types.js';
import {
convertNonPendingIDToNewSchema,
@@ -78,6 +75,7 @@
addLifecycleListener,
getCurrentLifecycleState,
} from '../lifecycle/lifecycle.js';
+import { commCoreModule } from '../native-modules.js';
import { replaceWithThreadActionType } from '../navigation/action-types.js';
import { activeMessageListSelector } from '../navigation/nav-selectors.js';
import { NavContext } from '../navigation/navigation-context.js';
@@ -100,7 +98,7 @@
// Navigation state
+activeThread: ?string,
// Redux state
- +unreadCount: number,
+ +unreadCount: { +[keyserverID: string]: number },
+deviceTokens: {
+[keyserverID: string]: ?string,
},
@@ -108,7 +106,6 @@
+[id: string]: ThreadInfo,
},
+notifPermissionAlertInfo: NotifPermissionAlertInfo,
- +connection: ConnectionInfo,
+updatesCurrentAsOf: number,
+activeTheme: ?GlobalTheme,
+loggedIn: boolean,
@@ -211,9 +208,7 @@
);
}
- if (this.props.connection.status === 'connected') {
- this.updateBadgeCount();
- }
+ void this.updateBadgeCount();
}
componentWillUnmount() {
@@ -267,14 +262,7 @@
if (this.props.activeThread !== prevProps.activeThread) {
this.clearNotifsOfThread();
}
-
- if (
- this.props.connection.status === 'connected' &&
- (prevProps.connection.status !== 'connected' ||
- this.props.unreadCount !== prevProps.unreadCount)
- ) {
- this.updateBadgeCount();
- }
+ void this.updateBadgeCount();
for (const threadID of this.openThreadOnceReceived) {
const threadInfo = this.props.threadInfos[threadID];
@@ -312,12 +300,41 @@
}
}
- updateBadgeCount() {
- const curUnreadCount = this.props.unreadCount;
+ async updateBadgeCount() {
+ const curUnreadCounts = this.props.unreadCount;
+
+ let totalUnreadCount = 0;
+ const notifStorageUnreadCounts: Array<{
+ +id: string,
+ unreadCount: number,
+ }> = [];
+
+ for (const keyserverID in curUnreadCounts) {
+ totalUnreadCount += curUnreadCounts[keyserverID];
+ notifStorageUnreadCounts.push({
+ id: keyserverID,
+ unreadCount: curUnreadCounts[keyserverID],
+ });
+ }
+
if (Platform.OS === 'ios') {
- CommIOSNotifications.setBadgesCount(curUnreadCount);
+ CommIOSNotifications.setBadgesCount(totalUnreadCount);
} else if (Platform.OS === 'android') {
- CommAndroidNotifications.setBadge(curUnreadCount);
+ CommAndroidNotifications.setBadge(totalUnreadCount);
+ }
+
+ try {
+ await commCoreModule.updateKeyserverDataInNotifStorage(
+ notifStorageUnreadCounts,
+ );
+ } catch (e) {
+ if (__DEV__) {
+ Alert.alert(
+ 'MMKV error',
+ 'Failed to update keyserver data in MMKV.' + e.message,
+ );
+ }
+ console.log(e);
}
}
@@ -699,16 +716,12 @@
React.memo<BaseProps>(function ConnectedPushHandler(props: BaseProps) {
const navContext = React.useContext(NavContext);
const activeThread = activeMessageListSelector(navContext);
- const boundUnreadCount = useSelector(unreadCount);
+ const boundUnreadCount = useSelector(extendedUnreadCount);
const deviceTokens = useSelector(deviceTokensSelector);
const threadInfos = useSelector(threadInfoSelector);
const notifPermissionAlertInfo = useSelector(
state => state.notifPermissionAlertInfo,
);
- const connection = useSelector(
- connectionSelector(authoritativeKeyserverID),
- );
- invariant(connection, 'keyserver missing from keyserverStore');
const updatesCurrentAsOf = useSelector(
updatesCurrentAsOfSelector(authoritativeKeyserverID),
);
@@ -728,7 +741,6 @@
deviceTokens={deviceTokens}
threadInfos={threadInfos}
notifPermissionAlertInfo={notifPermissionAlertInfo}
- connection={connection}
updatesCurrentAsOf={updatesCurrentAsOf}
activeTheme={activeTheme}
loggedIn={loggedIn}
diff --git a/native/redux/redux-utils.js b/native/redux/redux-utils.js
--- a/native/redux/redux-utils.js
+++ b/native/redux/redux-utils.js
@@ -2,7 +2,10 @@
import { useSelector as reactReduxUseSelector } from 'react-redux';
-import { keyserverStoreOpsHandlers } from 'lib/ops/keyserver-store-ops.js';
+import {
+ keyserverStoreOpsHandlers,
+ getKeyserversToRemoveFromNotifsStore,
+} from 'lib/ops/keyserver-store-ops.js';
import { messageStoreOpsHandlers } from 'lib/ops/message-store-ops.js';
import { reportStoreOpsHandlers } from 'lib/ops/report-store-ops.js';
import { threadStoreOpsHandlers } from 'lib/ops/thread-store-ops.js';
@@ -42,6 +45,8 @@
userStoreOpsHandlers.convertOpsToClientDBOps(userStoreOperations);
const convertedKeyserverStoreOperations =
keyserverStoreOpsHandlers.convertOpsToClientDBOps(keyserverStoreOperations);
+ const keyserversToRemoveFromNotifsStore =
+ getKeyserversToRemoveFromNotifsStore(keyserverStoreOperations);
try {
const promises = [];
@@ -83,6 +88,13 @@
),
);
}
+ if (keyserversToRemoveFromNotifsStore.length > 0) {
+ promises.push(
+ commCoreModule.removeKeyserverDataFromNotifStorage(
+ keyserversToRemoveFromNotifsStore,
+ ),
+ );
+ }
await Promise.all(promises);
} catch (e) {
if (isTaskCancelledError(e)) {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Jan 16, 3:13 AM (16 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5941576
Default Alt Text
D11090.1768533187.diff (17 KB)
Attached To
Mode
D11090: Update unread count storage from JS and NSE
Attached
Detach File
Event Timeline
Log In to Comment