Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32162101
D15441.1765044559.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
34 KB
Referenced Files
None
Subscribers
None
D15441.1765044559.diff
View Options
diff --git a/lib/types/notif-types.js b/lib/types/notif-types.js
--- a/lib/types/notif-types.js
+++ b/lib/types/notif-types.js
@@ -60,6 +60,9 @@
+unreadCount?: number,
+threadID: string,
+encryptionFailed?: '1',
+ +badge?: string,
+ +badgeOnly?: '1',
+ +farcasterBadge?: '1',
};
export type PlainTextWebNotification = $ReadOnly<{
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
@@ -58,6 +58,7 @@
private static final String KEYSERVER_ID_KEY = "keyserverID";
private static final String SENDER_DEVICE_ID_KEY = "senderDeviceID";
private static final String MESSAGE_TYPE_KEY = "type";
+ private static final String FARCASTER_BADGE_KEY = "farcasterBadge";
private static final String CHANNEL_ID = "default";
private static final long[] VIBRATION_SPEC = {500, 500};
private static final Map<Integer, String> NOTIF_PRIORITY_VERBOSE =
@@ -68,6 +69,7 @@
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_FARCASTER_KEY = "FARCASTER";
private Bitmap displayableNotificationLargeIcon;
private NotificationManager notificationManager;
private LocalBroadcastManager localBroadcastManager;
@@ -301,6 +303,22 @@
senderKeyserverUnreadCountKey, senderKeyserverUnreadCount);
}
+ if ("1".equals(message.getData().get(FARCASTER_BADGE_KEY)) &&
+ message.getData().get(BADGE_KEY) != null) {
+ String farcasterBadge = message.getData().get(BADGE_KEY);
+ try {
+ int farcasterBadgeCount = Integer.parseInt(farcasterBadge);
+ String farcasterUnreadCountKey = String.join(
+ MMKV_KEY_SEPARATOR,
+ MMKV_KEYSERVER_PREFIX,
+ MMKV_FARCASTER_KEY,
+ MMKV_UNREAD_COUNT_SUFFIX);
+ CommMMKV.setInt(farcasterUnreadCountKey, farcasterBadgeCount);
+ } catch (NumberFormatException e) {
+ Log.w("COMM", "Invalid Farcaster badge count", e);
+ }
+ }
+
if (message.getData().get(SENDER_DEVICE_ID_KEY) != null &&
message.getData().get(THREAD_ID_KEY) != null &&
message.getData().get(RESCIND_KEY) != null) {
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
@@ -116,15 +116,15 @@
virtual jsi::Value isNotificationsSessionInitializedWithDevices(
jsi::Runtime &rt,
jsi::Array deviceIDs) override;
- virtual jsi::Value updateKeyserverDataInNotifStorage(
+ virtual jsi::Value updateDataInNotifStorage(
jsi::Runtime &rt,
- jsi::Array keyserversData) override;
- virtual jsi::Value removeKeyserverDataFromNotifStorage(
+ jsi::Array data) override;
+ virtual jsi::Value removeDataFromNotifStorage(
jsi::Runtime &rt,
- jsi::Array keyserverIDsToDelete) override;
- virtual jsi::Value getKeyserverDataFromNotifStorage(
+ jsi::Array idsToDelete) override;
+ virtual jsi::Value getDataFromNotifStorage(
jsi::Runtime &rt,
- jsi::Array keyserverIDs) override;
+ jsi::Array ids) override;
virtual jsi::Value updateUnreadThickThreadsInNotifsStorage(
jsi::Runtime &rt,
jsi::Array unreadThickThreadIDs) override;
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
@@ -1328,26 +1328,26 @@
});
}
-jsi::Value CommCoreModule::updateKeyserverDataInNotifStorage(
+jsi::Value CommCoreModule::updateDataInNotifStorage(
jsi::Runtime &rt,
- jsi::Array keyserversData) {
-
- std::vector<std::pair<std::string, int>> keyserversDataCpp;
- for (auto idx = 0; idx < keyserversData.size(rt); idx++) {
- auto data = keyserversData.getValueAtIndex(rt, idx).asObject(rt);
- std::string keyserverID = data.getProperty(rt, "id").asString(rt).utf8(rt);
- std::string keyserverUnreadCountKey =
- "KEYSERVER." + keyserverID + ".UNREAD_COUNT";
- int unreadCount = data.getProperty(rt, "unreadCount").asNumber();
- keyserversDataCpp.push_back({keyserverUnreadCountKey, unreadCount});
+ jsi::Array data) {
+
+ std::vector<std::pair<std::string, int>> dataVectorCpp;
+ for (auto idx = 0; idx < data.size(rt); idx++) {
+ auto dataItem = data.getValueAtIndex(rt, idx).asObject(rt);
+ std::string id = dataItem.getProperty(rt, "id").asString(rt).utf8(rt);
+ std::string storageKey = "KEYSERVER." + id + ".UNREAD_COUNT";
+
+ int unreadCount = dataItem.getProperty(rt, "unreadCount").asNumber();
+ dataVectorCpp.push_back({storageKey, unreadCount});
}
return createPromiseAsJSIValue(
rt, [=](jsi::Runtime &innerRt, std::shared_ptr<Promise> promise) {
std::string error;
try {
- for (const auto &keyserverData : keyserversDataCpp) {
- CommMMKV::setInt(keyserverData.first, keyserverData.second);
+ for (const auto &dataItem : dataVectorCpp) {
+ CommMMKV::setInt(dataItem.first, dataItem.second);
}
} catch (const std::exception &e) {
error = e.what();
@@ -1363,23 +1363,22 @@
});
}
-jsi::Value CommCoreModule::removeKeyserverDataFromNotifStorage(
+jsi::Value CommCoreModule::removeDataFromNotifStorage(
jsi::Runtime &rt,
- jsi::Array keyserverIDsToDelete) {
- std::vector<std::string> keyserverIDsToDeleteCpp{};
- for (auto idx = 0; idx < keyserverIDsToDelete.size(rt); idx++) {
- std::string keyserverID =
- keyserverIDsToDelete.getValueAtIndex(rt, idx).asString(rt).utf8(rt);
- std::string keyserverUnreadCountKey =
- "KEYSERVER." + keyserverID + ".UNREAD_COUNT";
- keyserverIDsToDeleteCpp.push_back(keyserverUnreadCountKey);
+ jsi::Array idsToDelete) {
+ std::vector<std::string> keysToDeleteCpp{};
+ for (auto idx = 0; idx < idsToDelete.size(rt); idx++) {
+ std::string id =
+ idsToDelete.getValueAtIndex(rt, idx).asString(rt).utf8(rt);
+ std::string storageKey = "KEYSERVER." + id + ".UNREAD_COUNT";
+ keysToDeleteCpp.push_back(storageKey);
}
return createPromiseAsJSIValue(
rt, [=](jsi::Runtime &innerRt, std::shared_ptr<Promise> promise) {
std::string error;
try {
- CommMMKV::removeKeys(keyserverIDsToDeleteCpp);
+ CommMMKV::removeKeys(keysToDeleteCpp);
} catch (const std::exception &e) {
error = e.what();
}
@@ -1394,65 +1393,64 @@
});
}
-jsi::Value CommCoreModule::getKeyserverDataFromNotifStorage(
+jsi::Value CommCoreModule::getDataFromNotifStorage(
jsi::Runtime &rt,
- jsi::Array keyserverIDs) {
- std::vector<std::string> keyserverIDsCpp{};
- for (auto idx = 0; idx < keyserverIDs.size(rt); idx++) {
- std::string keyserverID =
- keyserverIDs.getValueAtIndex(rt, idx).asString(rt).utf8(rt);
- keyserverIDsCpp.push_back(keyserverID);
+ jsi::Array ids) {
+ std::vector<std::string> idsCpp{};
+ for (auto idx = 0; idx < ids.size(rt); idx++) {
+ std::string id =
+ ids.getValueAtIndex(rt, idx).asString(rt).utf8(rt);
+ idsCpp.push_back(id);
}
return createPromiseAsJSIValue(
rt, [=](jsi::Runtime &innerRt, std::shared_ptr<Promise> promise) {
std::string error;
- std::vector<std::pair<std::string, int>> keyserversDataVector{};
+ std::vector<std::pair<std::string, int>> dataVector{};
try {
- for (const auto &keyserverID : keyserverIDsCpp) {
- std::string keyserverUnreadCountKey =
- "KEYSERVER." + keyserverID + ".UNREAD_COUNT";
+ for (const auto &id : idsCpp) {
+ std::string storageKey = "KEYSERVER." + id + ".UNREAD_COUNT";
std::optional<int> unreadCount =
- CommMMKV::getInt(keyserverUnreadCountKey, -1);
+ CommMMKV::getInt(storageKey, -1);
if (!unreadCount.has_value()) {
continue;
}
- keyserversDataVector.push_back({keyserverID, unreadCount.value()});
+ dataVector.push_back({id, unreadCount.value()});
}
} catch (const std::exception &e) {
error = e.what();
}
- auto keyserversDataVectorPtr =
+ auto dataVectorPtr =
std::make_shared<std::vector<std::pair<std::string, int>>>(
- std::move(keyserversDataVector));
+ std::move(dataVector));
this->jsInvoker_->invokeAsync(
- [&innerRt, keyserversDataVectorPtr, error, promise]() {
+ [&innerRt, dataVectorPtr, error, promise]() {
if (error.size()) {
promise->reject(error);
return;
}
- size_t numKeyserversData = keyserversDataVectorPtr->size();
- jsi::Array jsiKeyserversData =
- jsi::Array(innerRt, numKeyserversData);
+ size_t numData = dataVectorPtr->size();
+ jsi::Array jsiData =
+ jsi::Array(innerRt, numData);
size_t writeIdx = 0;
- for (const auto &keyserverData : *keyserversDataVectorPtr) {
- jsi::Object jsiKeyserverData = jsi::Object(innerRt);
- jsiKeyserverData.setProperty(
- innerRt, "id", keyserverData.first);
- jsiKeyserverData.setProperty(
- innerRt, "unreadCount", keyserverData.second);
- jsiKeyserversData.setValueAtIndex(
- innerRt, writeIdx++, jsiKeyserverData);
+ for (const auto &dataItem : *dataVectorPtr) {
+ jsi::Object jsiDataItem = jsi::Object(innerRt);
+ jsiDataItem.setProperty(
+ innerRt, "id", dataItem.first);
+ jsiDataItem.setProperty(
+ innerRt, "unreadCount", dataItem.second);
+ jsiData.setValueAtIndex(
+ innerRt, writeIdx++, jsiDataItem);
}
- promise->resolve(std::move(jsiKeyserversData));
+ promise->resolve(std::move(jsiData));
});
});
}
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
@@ -66,14 +66,14 @@
static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isNotificationsSessionInitializedWithDevices(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
return static_cast<CommCoreModuleSchemaCxxSpecJSI *>(&turboModule)->isNotificationsSessionInitializedWithDevices(rt, args[0].asObject(rt).asArray(rt));
}
-static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateKeyserverDataInNotifStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
- return static_cast<CommCoreModuleSchemaCxxSpecJSI *>(&turboModule)->updateKeyserverDataInNotifStorage(rt, args[0].asObject(rt).asArray(rt));
+static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateDataInNotifStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
+ return static_cast<CommCoreModuleSchemaCxxSpecJSI *>(&turboModule)->updateDataInNotifStorage(rt, args[0].asObject(rt).asArray(rt));
}
-static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeKeyserverDataFromNotifStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
- return static_cast<CommCoreModuleSchemaCxxSpecJSI *>(&turboModule)->removeKeyserverDataFromNotifStorage(rt, args[0].asObject(rt).asArray(rt));
+static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeDataFromNotifStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
+ return static_cast<CommCoreModuleSchemaCxxSpecJSI *>(&turboModule)->removeDataFromNotifStorage(rt, args[0].asObject(rt).asArray(rt));
}
-static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getKeyserverDataFromNotifStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
- return static_cast<CommCoreModuleSchemaCxxSpecJSI *>(&turboModule)->getKeyserverDataFromNotifStorage(rt, args[0].asObject(rt).asArray(rt));
+static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDataFromNotifStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
+ return static_cast<CommCoreModuleSchemaCxxSpecJSI *>(&turboModule)->getDataFromNotifStorage(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<CommCoreModuleSchemaCxxSpecJSI *>(&turboModule)->updateUnreadThickThreadsInNotifsStorage(rt, args[0].asObject(rt).asArray(rt));
@@ -270,9 +270,9 @@
methodMap_["isNotificationsSessionInitialized"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isNotificationsSessionInitialized};
methodMap_["isDeviceNotificationsSessionInitialized"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isDeviceNotificationsSessionInitialized};
methodMap_["isNotificationsSessionInitializedWithDevices"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isNotificationsSessionInitializedWithDevices};
- methodMap_["updateKeyserverDataInNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateKeyserverDataInNotifStorage};
- methodMap_["removeKeyserverDataFromNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeKeyserverDataFromNotifStorage};
- methodMap_["getKeyserverDataFromNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getKeyserverDataFromNotifStorage};
+ methodMap_["updateDataInNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateDataInNotifStorage};
+ methodMap_["removeDataFromNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeDataFromNotifStorage};
+ methodMap_["getDataFromNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getDataFromNotifStorage};
methodMap_["updateUnreadThickThreadsInNotifsStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateUnreadThickThreadsInNotifsStorage};
methodMap_["getUnreadThickThreadIDsFromNotifsStorage"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getUnreadThickThreadIDsFromNotifsStorage};
methodMap_["initializeContentOutboundSession"] = MethodMetadata {5, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeContentOutboundSession};
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
@@ -37,9 +37,9 @@
virtual jsi::Value isNotificationsSessionInitialized(jsi::Runtime &rt) = 0;
virtual jsi::Value isDeviceNotificationsSessionInitialized(jsi::Runtime &rt, jsi::String deviceID) = 0;
virtual jsi::Value isNotificationsSessionInitializedWithDevices(jsi::Runtime &rt, jsi::Array deviceIDs) = 0;
- 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 updateDataInNotifStorage(jsi::Runtime &rt, jsi::Array data) = 0;
+ virtual jsi::Value removeDataFromNotifStorage(jsi::Runtime &rt, jsi::Array idsToDelete) = 0;
+ virtual jsi::Value getDataFromNotifStorage(jsi::Runtime &rt, jsi::Array ids) = 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<jsi::String> oneTimeKey, jsi::String deviceID) = 0;
@@ -254,29 +254,29 @@
return bridging::callFromJs<jsi::Value>(
rt, &T::isNotificationsSessionInitializedWithDevices, jsInvoker_, instance_, std::move(deviceIDs));
}
- jsi::Value updateKeyserverDataInNotifStorage(jsi::Runtime &rt, jsi::Array keyserversData) override {
+ jsi::Value updateDataInNotifStorage(jsi::Runtime &rt, jsi::Array data) override {
static_assert(
- bridging::getParameterCount(&T::updateKeyserverDataInNotifStorage) == 2,
- "Expected updateKeyserverDataInNotifStorage(...) to have 2 parameters");
+ bridging::getParameterCount(&T::updateDataInNotifStorage) == 2,
+ "Expected updateDataInNotifStorage(...) to have 2 parameters");
return bridging::callFromJs<jsi::Value>(
- rt, &T::updateKeyserverDataInNotifStorage, jsInvoker_, instance_, std::move(keyserversData));
+ rt, &T::updateDataInNotifStorage, jsInvoker_, instance_, std::move(data));
}
- jsi::Value removeKeyserverDataFromNotifStorage(jsi::Runtime &rt, jsi::Array keyserverIDsToDelete) override {
+ jsi::Value removeDataFromNotifStorage(jsi::Runtime &rt, jsi::Array idsToDelete) override {
static_assert(
- bridging::getParameterCount(&T::removeKeyserverDataFromNotifStorage) == 2,
- "Expected removeKeyserverDataFromNotifStorage(...) to have 2 parameters");
+ bridging::getParameterCount(&T::removeDataFromNotifStorage) == 2,
+ "Expected removeDataFromNotifStorage(...) to have 2 parameters");
return bridging::callFromJs<jsi::Value>(
- rt, &T::removeKeyserverDataFromNotifStorage, jsInvoker_, instance_, std::move(keyserverIDsToDelete));
+ rt, &T::removeDataFromNotifStorage, jsInvoker_, instance_, std::move(idsToDelete));
}
- jsi::Value getKeyserverDataFromNotifStorage(jsi::Runtime &rt, jsi::Array keyserverIDs) override {
+ jsi::Value getDataFromNotifStorage(jsi::Runtime &rt, jsi::Array ids) override {
static_assert(
- bridging::getParameterCount(&T::getKeyserverDataFromNotifStorage) == 2,
- "Expected getKeyserverDataFromNotifStorage(...) to have 2 parameters");
+ bridging::getParameterCount(&T::getDataFromNotifStorage) == 2,
+ "Expected getDataFromNotifStorage(...) to have 2 parameters");
return bridging::callFromJs<jsi::Value>(
- rt, &T::getKeyserverDataFromNotifStorage, jsInvoker_, instance_, std::move(keyserverIDs));
+ rt, &T::getDataFromNotifStorage, jsInvoker_, instance_, std::move(ids));
}
jsi::Value updateUnreadThickThreadsInNotifsStorage(jsi::Runtime &rt, jsi::Array unreadThickThreadIDs) override {
static_assert(
diff --git a/native/database/sqlite-api.js b/native/database/sqlite-api.js
--- a/native/database/sqlite-api.js
+++ b/native/database/sqlite-api.js
@@ -59,7 +59,7 @@
const promises = [];
if (keyserversToRemoveFromNotifsStore.length > 0) {
promises.push(
- commCoreModule.removeKeyserverDataFromNotifStorage(
+ commCoreModule.removeDataFromNotifStorage(
keyserversToRemoveFromNotifsStore,
),
);
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
@@ -24,12 +24,14 @@
NSString *const encryptionKeyLabel = @"encryptionKey";
NSString *const needsSilentBadgeUpdateKey = @"needsSilentBadgeUpdate";
NSString *const notificationIdKey = @"notificationId";
+NSString *const farcasterBadgeKey = @"farcasterBadge";
// Those and future MMKV-related constants should match
// similar constants in CommNotificationsHandler.java
const std::string mmkvKeySeparator = ".";
const std::string mmkvKeyserverPrefix = "KEYSERVER";
const std::string mmkvUnreadCountSuffix = "UNREAD_COUNT";
+const std::string mmkvFarcasterKey = "FARCASTER";
// The context for this constant can be found here:
// https://linear.app/comm/issue/ENG-3074#comment-bd2f5e28
@@ -529,6 +531,16 @@
senderKeyserverUnreadCountKey, senderKeyserverUnreadCount);
}
+ if (content.userInfo[farcasterBadgeKey] &&
+ [content.userInfo[farcasterBadgeKey] isEqualToString:@"1"] &&
+ content.userInfo[@"badge"]) {
+ int farcasterBadgeCount = [content.userInfo[@"badge"] intValue];
+ std::string farcasterUnreadCountKey = joinStrings(
+ mmkvKeySeparator,
+ {mmkvKeyserverPrefix, mmkvFarcasterKey, mmkvUnreadCountSuffix});
+ comm::CommMMKV::setInt(farcasterUnreadCountKey, farcasterBadgeCount);
+ }
+
if (content.userInfo[senderDeviceIDKey] && content.userInfo[threadIDKey] &&
[self isRescind:content.userInfo]) {
comm::CommMMKV::removeElementFromStringSet(
@@ -541,7 +553,7 @@
std::string([content.userInfo[threadIDKey] UTF8String]));
}
- // calculate unread counts from keyservers
+ // calculate unread counts from keyservers and Farcaster
int totalUnreadCount = 0;
std::vector<std::string> allKeys = comm::CommMMKV::getAllKeys();
for (const auto &key : allKeys) {
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
@@ -362,9 +362,9 @@
let unreadThickThreadIDs: $ReadOnlyArray<string>;
try {
[queriedKeyserverData, unreadThickThreadIDs] = await Promise.all([
- commCoreModule.getKeyserverDataFromNotifStorage(notifsStorageQueries),
+ commCoreModule.getDataFromNotifStorage(notifsStorageQueries),
handleUnreadThickThreadIDsInNotifsStorage,
- commCoreModule.updateKeyserverDataInNotifStorage(notifStorageUpdates),
+ commCoreModule.updateDataInNotifStorage(notifStorageUpdates),
]);
} catch (e) {
if (__DEV__) {
@@ -386,6 +386,20 @@
}
totalUnreadCount += unreadThickThreadIDs.length;
+
+ let farcasterUnreadCount = 0;
+ try {
+ const farcasterData =
+ await commCoreModule.getDataFromNotifStorage(['FARCASTER']);
+ if (farcasterData.length > 0) {
+ farcasterUnreadCount = farcasterData[0].unreadCount;
+ }
+ } catch (e) {
+ console.error('Failed to get Farcaster unread count:', e);
+ }
+
+ totalUnreadCount += farcasterUnreadCount;
+
if (Platform.OS === 'ios') {
CommIOSNotifications.setBadgesCount(totalUnreadCount);
} else if (Platform.OS === 'android') {
@@ -398,7 +412,7 @@
this.props.thinThreadsUnreadCount,
);
try {
- await commCoreModule.removeKeyserverDataFromNotifStorage(
+ await commCoreModule.removeDataFromNotifStorage(
keyserversDataToRemove,
);
} catch (e) {
diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js
--- a/native/schema/CommCoreModuleSchema.js
+++ b/native/schema/CommCoreModuleSchema.js
@@ -79,14 +79,14 @@
+isNotificationsSessionInitializedWithDevices: (
deviceIDs: $ReadOnlyArray<string>,
) => Promise<{ +[deviceID: string]: boolean }>;
- +updateKeyserverDataInNotifStorage: (
- keyserversData: $ReadOnlyArray<{ +id: string, +unreadCount: number }>,
+ +updateDataInNotifStorage: (
+ data: $ReadOnlyArray<{ +id: string, +unreadCount: number }>,
) => Promise<void>;
- +removeKeyserverDataFromNotifStorage: (
- keyserverIDsToDelete: $ReadOnlyArray<string>,
+ +removeDataFromNotifStorage: (
+ idsToDelete: $ReadOnlyArray<string>,
) => Promise<void>;
- +getKeyserverDataFromNotifStorage: (
- keyserverIDs: $ReadOnlyArray<string>,
+ +getDataFromNotifStorage: (
+ ids: $ReadOnlyArray<string>,
) => Promise<$ReadOnlyArray<{ +id: string, +unreadCount: number }>>;
+updateUnreadThickThreadsInNotifsStorage: (
unreadThickThreadIDs: $ReadOnlyArray<string>,
diff --git a/services/tunnelbroker/src/notifs/generic_client.rs b/services/tunnelbroker/src/notifs/generic_client.rs
--- a/services/tunnelbroker/src/notifs/generic_client.rs
+++ b/services/tunnelbroker/src/notifs/generic_client.rs
@@ -66,9 +66,18 @@
#[derive(Clone, Debug, Serialize)]
pub struct GenericNotifPayload {
- pub title: String,
- pub body: String,
- pub thread_id: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub title: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub body: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub thread_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub badge: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub badge_only: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub farcaster_badge: Option<bool>,
}
enum APNsTopic {
@@ -135,36 +144,66 @@
apns_collapse_id: None,
};
- let payload = json!({
+ let mut payload_obj = json!({
"aps": {
- "alert": {
- "title": self.title,
- "body": self.body,
- },
- "thread-id": self.thread_id,
- "sound": "default",
"mutable-content": 1
},
});
+ // Only add alert if we have title or body
+ if self.title.is_some() || self.body.is_some() {
+ payload_obj["aps"]["alert"] = json!({
+ "title": self.title.unwrap_or_default(),
+ "body": self.body.unwrap_or_default(),
+ });
+ payload_obj["aps"]["sound"] = json!("default");
+ }
+
+ if let Some(thread_id) = self.thread_id {
+ payload_obj["aps"]["thread-id"] = json!(thread_id);
+ }
+ if let Some(badge) = self.badge {
+ payload_obj["badge"] = json!(badge);
+ }
+ if let Some(badge_only) = self.badge_only {
+ payload_obj["badgeOnly"] = json!(if badge_only { "1" } else { "0" });
+ }
+ if let Some(farcaster_badge) = self.farcaster_badge {
+ payload_obj["farcasterBadge"] =
+ json!(if farcaster_badge { "1" } else { "0" });
+ }
+
APNsNotif {
device_token: device_token.to_string(),
headers,
- payload: serde_json::to_string(&payload).unwrap(),
+ payload: serde_json::to_string(&payload_obj).unwrap(),
}
}
fn into_fcm(self, device_token: &str) -> FCMMessage {
use super::fcm::firebase_message::{AndroidConfig, AndroidMessagePriority};
- let data = json!({
+ let mut data = json!({
"id": uuid::Uuid::new_v4().to_string(),
- "title": self.title,
- "body": self.body,
- "threadID": self.thread_id,
- "badgeOnly": "0",
+ "badgeOnly": if self.badge_only.unwrap_or(false) { "1" } else { "0" },
});
+ if let Some(title) = self.title {
+ data["title"] = json!(title);
+ }
+ if let Some(body) = self.body {
+ data["body"] = json!(body);
+ }
+ if let Some(thread_id) = self.thread_id {
+ data["threadID"] = json!(thread_id);
+ }
+ if let Some(badge) = self.badge {
+ data["badge"] = json!(badge);
+ }
+ if let Some(farcaster_badge) = self.farcaster_badge {
+ data["farcasterBadge"] = json!(if farcaster_badge { "1" } else { "0" });
+ }
+
FCMMessage {
data,
token: device_token.to_string(),
@@ -177,13 +216,30 @@
fn into_web_push(self, device_token: &str) -> WebPushNotif {
use crate::notifs::web_push::WebPushNotif;
- let payload = json!({
+ let mut payload = json!({
"id": uuid::Uuid::new_v4().to_string(),
- "title": self.title,
- "body": self.body,
- "threadID": self.thread_id,
});
+ if let Some(title) = self.title {
+ payload["title"] = json!(title);
+ }
+ if let Some(body) = self.body {
+ payload["body"] = json!(body);
+ }
+ if let Some(thread_id) = self.thread_id {
+ payload["threadID"] = json!(thread_id);
+ }
+ if let Some(badge) = self.badge {
+ payload["badge"] = json!(badge);
+ }
+ if let Some(badge_only) = self.badge_only {
+ payload["badgeOnly"] = json!(if badge_only { "1" } else { "0" });
+ }
+ if let Some(farcaster_badge) = self.farcaster_badge {
+ payload["farcasterBadge"] =
+ json!(if farcaster_badge { "1" } else { "0" });
+ }
+
WebPushNotif {
device_token: device_token.to_string(),
payload: serde_json::to_string(&payload).unwrap(),
@@ -191,11 +247,27 @@
}
fn into_wns(self, device_token: &str) -> WNSNotif {
- let payload = json!({
- "title": self.title,
- "body": self.body,
- "threadID": self.thread_id,
- });
+ let mut payload = json!({});
+
+ if let Some(title) = self.title {
+ payload["title"] = json!(title);
+ }
+ if let Some(body) = self.body {
+ payload["body"] = json!(body);
+ }
+ if let Some(thread_id) = self.thread_id {
+ payload["threadID"] = json!(thread_id);
+ }
+ if let Some(badge) = self.badge {
+ payload["badge"] = json!(badge);
+ }
+ if let Some(badge_only) = self.badge_only {
+ payload["badgeOnly"] = json!(if badge_only { "1" } else { "0" });
+ }
+ if let Some(farcaster_badge) = self.farcaster_badge {
+ payload["farcasterBadge"] =
+ json!(if farcaster_badge { "1" } else { "0" });
+ }
WNSNotif {
device_token: device_token.to_string(),
diff --git a/services/tunnelbroker/src/token_distributor/notif_utils.rs b/services/tunnelbroker/src/token_distributor/notif_utils.rs
--- a/services/tunnelbroker/src/token_distributor/notif_utils.rs
+++ b/services/tunnelbroker/src/token_distributor/notif_utils.rs
@@ -58,9 +58,12 @@
};
Some(GenericNotifPayload {
- title: trim_text(title, 100),
- body: trim_text(&body, 300),
- thread_id: format!("FARCASTER#{}", conversation_id),
+ title: Some(trim_text(title, 100)),
+ body: Some(trim_text(&body, 300)),
+ thread_id: Some(format!("FARCASTER#{}", conversation_id)),
+ badge: None,
+ badge_only: None,
+ farcaster_badge: None,
})
}
diff --git a/services/tunnelbroker/src/token_distributor/token_connection.rs b/services/tunnelbroker/src/token_distributor/token_connection.rs
--- a/services/tunnelbroker/src/token_distributor/token_connection.rs
+++ b/services/tunnelbroker/src/token_distributor/token_connection.rs
@@ -6,7 +6,9 @@
use crate::database::DatabaseClient;
use crate::farcaster::FarcasterClient;
use crate::log::redact_sensitive_data;
-use crate::notifs::{GenericNotifClient, NotifRecipientDescriptor};
+use crate::notifs::{
+ GenericNotifClient, GenericNotifPayload, NotifRecipientDescriptor,
+};
use crate::token_distributor::config::TokenDistributorConfig;
use crate::token_distributor::error::TokenConnectionError;
use crate::token_distributor::notif_utils::prepare_notif_payload;
@@ -341,7 +343,7 @@
debug!("Processing refresh-self-direct-casts-inbox message");
}
FarcasterPayload::Unseen { .. } => {
- if let Err(e) = self.handle_unseen_message(&mut client).await {
+ if let Err(e) = self.handle_unseen_message(&farcaster_msg, &mut client).await {
info!(
metricType = "TokenDistributor_ConnectionFailure",
metricValue = 1,
@@ -667,8 +669,24 @@
async fn handle_unseen_message(
&self,
+ farcaster_msg: &FarcasterMessage,
client: &mut ChainedInterceptedServicesAuthClient,
) -> Result<(), TokenConnectionError> {
+ let inbox_count = if let Some(data_str) = &farcaster_msg.data {
+ match serde_json::from_str::<serde_json::Value>(data_str) {
+ Ok(data_json) => data_json
+ .get("inboxCount")
+ .and_then(|v| v.as_u64())
+ .map(|v| v as i32),
+ Err(e) => {
+ warn!("Failed to parse unseen message data: {}", e);
+ None
+ }
+ }
+ } else {
+ None
+ };
+
let conversations = self
.farcaster_client
.fetch_inbox(&self.token_info.user_id)
@@ -707,6 +725,38 @@
let recipient_devices = self.get_self_user_device_list(client).await?;
+ if let Some(count) = inbox_count {
+ for (device_id, platform_details) in &recipient_devices {
+ let Ok(platform) = platform_details.device_type().try_into() else {
+ continue;
+ };
+
+ let target = NotifRecipientDescriptor {
+ platform,
+ device_id: device_id.to_string(),
+ };
+
+ // Send badge-only notification using GenericNotifPayload
+ let badge_payload = GenericNotifPayload {
+ title: None,
+ body: None,
+ thread_id: None,
+ badge: Some(count.to_string()),
+ badge_only: Some(true),
+ farcaster_badge: Some(true),
+ };
+
+ if let Err(err) =
+ self.notif_client.send_notif(badge_payload, target).await
+ {
+ if !err.is_invalid_token() {
+ tracing::error!("Failed to send Farcaster badge notif: {:?}", err);
+ }
+ }
+ }
+ }
+
+ // Send inbox status messages to all devices
for (device_id, _) in &recipient_devices {
self
.message_sender
diff --git a/web/push-notif/badge-handler.react.js b/web/push-notif/badge-handler.react.js
--- a/web/push-notif/badge-handler.react.js
+++ b/web/push-notif/badge-handler.react.js
@@ -31,7 +31,7 @@
const unreadCountUpdates: {
[keyserverID: string]: number,
} = {};
- const unreadCountQueries: Array<string> = [];
+ const unreadCountQueries: Array<string> = ['FARCASTER'];
for (const keyserverID in thinThreadsUnreadCount) {
if (connection[keyserverID]?.status !== 'connected') {
@@ -76,6 +76,7 @@
}
totalUnreadCount += unreadThickThreadIDs.length;
+
document.title = getTitle(totalUnreadCount);
electron?.setBadge(totalUnreadCount === 0 ? null : totalUnreadCount);
})();
diff --git a/web/push-notif/service-worker.js b/web/push-notif/service-worker.js
--- a/web/push-notif/service-worker.js
+++ b/web/push-notif/service-worker.js
@@ -100,6 +100,17 @@
event.waitUntil(
(async () => {
+ if (data.farcasterBadge === '1' && data.badgeOnly === '1') {
+ const farcasterBadgeCount = parseInt(data.badge, 10) || 0;
+ // Use the same key format as queryNotifsUnreadCountStorage
+ // KEYSERVER:FARCASTER:unreadCount
+ await localforage.setItem(
+ 'keyserver:FARCASTER:unreadCount',
+ farcasterBadgeCount,
+ );
+ return;
+ }
+
let plainTextData: PlainTextWebNotification;
let decryptionResult: PlainTextWebNotification | WebNotifDecryptionError;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 6, 6:09 PM (18 h, 26 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5840007
Default Alt Text
D15441.1765044559.diff (34 KB)
Attached To
Mode
D15441: [tunnelbroker][lib][native][web] Handle Farcaster badge count notifs
Attached
Detach File
Event Timeline
Log In to Comment