From 63cb7a75b34f66fb00f3fae8820a4b4bb9a511e7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 17 May 2023 10:31:27 +0200 Subject: [PATCH 1/3] ios: update reactions api (#2451) --- apps/ios/Shared/Model/SimpleXAPI.swift | 8 ++--- apps/ios/Shared/Views/Chat/ChatView.swift | 12 +++---- apps/ios/SimpleXChat/APITypes.swift | 8 ++--- apps/ios/SimpleXChat/ChatTypes.swift | 44 +++++++++++++---------- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 8dcdb8514a..33e10bf077 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -345,9 +345,9 @@ func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent throw r } -func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, reaction: MsgReaction, add: Bool) async throws -> ChatItem { - let r = await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, reaction: reaction, add: add), bgDelay: msgDelay) - if case let .chatItemReaction(_, reaction, _) = r { return reaction.chatReaction.chatItem } +func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem { + let r = await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay) + if case let .chatItemReaction(_, _, reaction) = r { return reaction.chatReaction.chatItem } throw r } @@ -1285,7 +1285,7 @@ func processReceivedMsg(_ res: ChatResponse) async { } case let .chatItemUpdated(user, aChatItem): chatItemSimpleUpdate(user, aChatItem) - case let .chatItemReaction(user, r, _): + case let .chatItemReaction(user, _, r): if active(user) { m.updateChatItem(r.chatInfo, r.chatReaction.chatItem) } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 5df8d062fa..4cbf2ed7a0 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -526,7 +526,7 @@ struct ChatView: View { if chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted) { v.onTapGesture { - setReaction(r.reaction, add: !r.userReacted) + setReaction(add: !r.userReacted, reaction: r.reaction) } } else { v @@ -606,27 +606,27 @@ struct ChatView: View { let rs = MsgReaction.values.compactMap { r in ci.reactions.contains(where: { $0.userReacted && $0.reaction == r }) ? nil - : UIAction(title: r.text) { _ in setReaction(r, add: true) } + : UIAction(title: r.text) { _ in setReaction(add: true, reaction: r) } } if rs.count > 0 { return UIMenu( title: NSLocalizedString("React...", comment: "chat item menu"), - image: UIImage(systemName: "hand.thumbsup"), + image: UIImage(systemName: "face.smiling"), children: rs ) } return nil } - private func setReaction(_ r: MsgReaction, add: Bool) { + private func setReaction(add: Bool, reaction: MsgReaction) { Task { do { let chatItem = try await apiChatItemReaction( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, itemId: ci.id, - reaction: r, - add: add + add: add, + reaction: reaction ) await MainActor.run { ChatModel.shared.updateChatItem(chat.chatInfo, chatItem) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index cc1d0183f4..be86f32e45 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -41,7 +41,7 @@ public enum ChatCommand { case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool) case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) case apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64) - case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, reaction: MsgReaction, add: Bool) + case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) case apiGetNtfToken case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) case apiVerifyToken(token: DeviceToken, nonce: String, code: String) @@ -149,7 +149,7 @@ public enum ChatCommand { case let .apiUpdateChatItem(type, id, itemId, mc, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(mc.cmdString)" case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)" case let .apiDeleteMemberChatItem(groupId, groupMemberId, itemId): return "/_delete member item #\(groupId) \(groupMemberId) \(itemId)" - case let .apiChatItemReaction(type, id, itemId, reaction, add): return "/_reaction \(ref(type, id)) \(itemId) \(reaction.cmdString) \(onOff(add))" + case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" case .apiGetNtfToken: return "/_ntf get " case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" @@ -433,7 +433,7 @@ public enum ChatResponse: Decodable, Error { case newChatItem(user: User, chatItem: AChatItem) case chatItemStatusUpdated(user: User, chatItem: AChatItem) case chatItemUpdated(user: User, chatItem: AChatItem) - case chatItemReaction(user: User, reaction: ACIReaction, added: Bool) + case chatItemReaction(user: User, added: Bool, reaction: ACIReaction) case chatItemDeleted(user: User, deletedChatItem: AChatItem, toChatItem: AChatItem?, byUser: Bool) case contactsList(user: User, contacts: [Contact]) // group events @@ -668,7 +668,7 @@ public enum ChatResponse: Decodable, Error { case let .newChatItem(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemReaction(u, reaction, added): return withUser(u, "added: \(added)\n\(String(describing: reaction))") + case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") case let .chatItemDeleted(u, deletedChatItem, toChatItem, byUser): return withUser(u, "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))\nbyUser: \(byUser)") case let .contactsList(u, contacts): return withUser(u, String(describing: contacts)) case let .groupCreated(u, groupInfo): return withUser(u, String(describing: groupInfo)) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index b3a472f1e2..f93d2a5f2f 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -561,7 +561,7 @@ public enum ChatFeature: String, Decodable, Feature { switch self { case .timedMessages: return "stopwatch" case .fullDelete: return "trash.slash" - case .reactions: return "hand.thumbsup" + case .reactions: return "face.smiling" case .voice: return "mic" case .calls: return "phone" } @@ -571,7 +571,7 @@ public enum ChatFeature: String, Decodable, Feature { switch self { case .timedMessages: return "stopwatch.fill" case .fullDelete: return "trash.slash.fill" - case .reactions: return "hand.thumbsup.fill" + case .reactions: return "face.smiling.fill" case .voice: return "mic.fill" case .calls: return "phone.fill" } @@ -696,7 +696,7 @@ public enum GroupFeature: String, Decodable, Feature { case .timedMessages: return "stopwatch" case .directMessages: return "arrow.left.and.right.circle" case .fullDelete: return "trash.slash" - case .reactions: return "hand.thumbsup" + case .reactions: return "face.smiling" case .voice: return "mic" } } @@ -706,7 +706,7 @@ public enum GroupFeature: String, Decodable, Feature { case .timedMessages: return "stopwatch.fill" case .directMessages: return "arrow.left.and.right.circle.fill" case .fullDelete: return "trash.slash.fill" - case .reactions: return "hand.thumbsup.fill" + case .reactions: return "face.smiling.fill" case .voice: return "mic.fill" } } @@ -2469,25 +2469,17 @@ public struct CIReactionCount: Decodable { } public enum MsgReaction: Hashable { - case emoji(MREmojiChar) - case unknown + case emoji(emoji: MREmojiChar) + case unknown(type: String) public var text: String { switch self { case let .emoji(emoji): return emoji.rawValue - case .unknown: return "" + case .unknown: return "?" } } - public var cmdString: String { - switch self { - case let .emoji(emoji): return emoji.cmdString - case .unknown: return "" - } - } - - public static var values: [MsgReaction] = - MREmojiChar.allCases.map { .emoji($0) } + public static var values: [MsgReaction] = MREmojiChar.allCases.map { .emoji(emoji: $0) } enum CodingKeys: String, CodingKey { case type @@ -2527,12 +2519,26 @@ extension MsgReaction: Decodable { switch type { case "emoji": let emoji = try container.decode(MREmojiChar.self, forKey: CodingKeys.emoji) - self = .emoji(emoji) + self = .emoji(emoji: emoji) default: - self = .unknown + self = .unknown(type: type) } } catch { - self = .unknown + self = .unknown(type: "") + } + } +} + +extension MsgReaction: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .emoji(emoji): + try container.encode("emoji", forKey: .type) + try container.encode(emoji, forKey: .emoji) + // TODO use original JSON and type + case let .unknown(type): + try container.encode(type, forKey: .type) } } } From e799e80843c955021beed6a3ed2732d429c5fe4f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 17 May 2023 13:31:24 +0400 Subject: [PATCH 2/3] ios: remove "off" from group preferences ttl picker; core: allow optional ttl for group preference (#2452) --- apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift | 2 +- src/Simplex/Chat.hs | 2 +- src/Simplex/Chat/Messages.hs | 2 +- src/Simplex/Chat/Types.hs | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 6d5832c809..eedf08d869 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -82,7 +82,7 @@ struct GroupPreferencesView: View { DropdownCustomTimePicker( selection: $preferences.timedMessages.ttl, label: "Delete after", - dropdownValues: TimedMessagesPreference.ttlValues, + dropdownValues: TimedMessagesPreference.ttlValues.filter { $0 != nil }, // TODO in 5.2 - allow "off" customPickerConfirmButtonText: "Select", customPickerDescription: "Delete after" ) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index b916f3f336..db2dcbf04b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1605,7 +1605,7 @@ processChatCommand = \case prefs' = setPreference' SCFTimedMessages pref_ $ Just userPreferences updateContactPrefs user ct prefs' SetGroupTimedMessages gName ttl_ -> do - let pref = uncurry TimedMessagesGroupPreference $ maybe (FEOff, 86400) (FEOn,) ttl_ + let pref = uncurry TimedMessagesGroupPreference $ maybe (FEOff, Just 86400) (\ttl -> (FEOn, Just ttl)) ttl_ updateGroupProfileByName gName $ \p -> p {groupPreferences = Just . setGroupPreference' SGFTimedMessages pref $ groupPreferences p} QuitChat -> liftIO exitSuccess diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 328fdffeb5..b0d48bd7af 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -376,7 +376,7 @@ contactTimedTTL Contact {mergedPreferences = ContactUserPreferences {timedMessag groupTimedTTL :: GroupInfo -> Maybe (Maybe Int) groupTimedTTL GroupInfo {fullGroupPreferences = FullGroupPreferences {timedMessages = TimedMessagesGroupPreference {enable, ttl}}} - | enable == FEOn = Just $ Just ttl + | enable == FEOn = Just ttl | otherwise = Nothing rcvContactCITimed :: Contact -> Maybe Int -> Maybe CITimed diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index fbfe7830bc..e46dcfa647 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -704,7 +704,7 @@ emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing defaultGroupPrefs :: FullGroupPreferences defaultGroupPrefs = FullGroupPreferences - { timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = 86400}, + { timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400}, directMessages = DirectMessagesGroupPreference {enable = FEOff}, fullDelete = FullDeleteGroupPreference {enable = FEOff}, -- receipts = GroupPreference {enable = FEOff}, @@ -796,7 +796,7 @@ data GroupPreference = GroupPreference data TimedMessagesGroupPreference = TimedMessagesGroupPreference { enable :: GroupFeatureEnabled, - ttl :: Int + ttl :: Maybe Int } deriving (Eq, Show, Generic, FromJSON) @@ -854,7 +854,7 @@ instance HasField "enable" VoiceGroupPreference GroupFeatureEnabled where instance GroupFeatureI 'GFTimedMessages where type GroupFeaturePreference 'GFTimedMessages = TimedMessagesGroupPreference sGroupFeature = SGFTimedMessages - groupPrefParam TimedMessagesGroupPreference {ttl} = Just ttl + groupPrefParam TimedMessagesGroupPreference {ttl} = ttl instance GroupFeatureI 'GFDirectMessages where type GroupFeaturePreference 'GFDirectMessages = DirectMessagesGroupPreference From b6a4f5f51865381b5485c9e75ff301f8da1716c4 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 17 May 2023 16:13:35 +0400 Subject: [PATCH 3/3] core: enable creation of decryption errors chat items (#2450) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 24 ++++++++++-------------- stack.yaml | 2 +- tests/ChatTests/Direct.hs | 2 +- tests/ChatTests/Groups.hs | 2 +- 6 files changed, 15 insertions(+), 19 deletions(-) diff --git a/cabal.project b/cabal.project index f587eb6b7f..c31558d2c6 100644 --- a/cabal.project +++ b/cabal.project @@ -7,7 +7,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: d693868bc0ce0926f5b890dfced56960c8bcf2ab + tag: 6c6f22051d693e00aaa4c4e6c5b6bff7e9a40b36 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 54821c6692..c516be2b0c 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."d693868bc0ce0926f5b890dfced56960c8bcf2ab" = "1g9p7b7da90ac3zrr0hwk6bjn3x3zrl0yq314f5w4a54zlg785zx"; + "https://github.com/simplex-chat/simplexmq.git"."6c6f22051d693e00aaa4c4e6c5b6bff7e9a40b36" = "0mi4p2s5klm2w6za8xljl3yaiva814phhm5n5q36f04a016i6llm"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb"; "https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index db2dcbf04b..817236bbf2 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2993,21 +2993,17 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do -- TODO add debugging output _ -> pure () - -- TODO revert to commented function to enable creation of decryption errors chat items agentMsgDecryptError :: AgentErrorType -> Maybe (MsgDecryptError, Word32) - agentMsgDecryptError _ = Nothing - - -- agentMsgDecryptError :: AgentErrorType -> Maybe (MsgDecryptError, Word32) - -- agentMsgDecryptError = \case - -- AGENT (A_CRYPTO RATCHET_HEADER) -> Just (MDERatchetHeader, 1) - -- AGENT (A_CRYPTO (RATCHET_SKIPPED n)) -> Just (MDETooManySkipped, n) - -- -- we are not treating this as decryption error, as in many cases it happens as the result of duplicate or redundant delivery, - -- -- and we don't have a way to differentiate. - -- -- we could store the hashes of past messages in the agent, or delaying message deletion after ACK - -- -- A_DUPLICATE -> Nothing - -- -- earlier messages may be received in case of redundant delivery, and do not necessarily indicate an error - -- -- AGENT (A_CRYPTO (RATCHET_EARLIER n)) -> Nothing - -- _ -> Nothing + agentMsgDecryptError = \case + AGENT (A_CRYPTO RATCHET_HEADER) -> Just (MDERatchetHeader, 1) + AGENT (A_CRYPTO (RATCHET_SKIPPED n)) -> Just (MDETooManySkipped, n) + -- we are not treating this as decryption error, as in many cases it happens as the result of duplicate or redundant delivery, + -- and we don't have a way to differentiate. + -- we could store the hashes of past messages in the agent, or delaying message deletion after ACK + -- A_DUPLICATE -> Nothing + -- earlier messages may be received in case of redundant delivery, and do not necessarily indicate an error + -- AGENT (A_CRYPTO (RATCHET_EARLIER n)) -> Nothing + _ -> Nothing mdeUpdatedCI :: (MsgDecryptError, Word32) -> CChatItem c -> Maybe (ChatItem c 'MDRcv, CIContent 'MDRcv) mdeUpdatedCI (mde', n') (CChatItem _ ci@ChatItem {content = CIRcvDecryptionError mde n}) diff --git a/stack.yaml b/stack.yaml index 4e3b3c81ea..16b4b7ca65 100644 --- a/stack.yaml +++ b/stack.yaml @@ -49,7 +49,7 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: d693868bc0ce0926f5b890dfced56960c8bcf2ab + commit: 6c6f22051d693e00aaa4c4e6c5b6bff7e9a40b36 - github: kazu-yamamoto/http2 commit: b5a1b7200cf5bc7044af34ba325284271f6dff25 # - ../direct-sqlcipher diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 1990012818..72ef1ad07e 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -84,7 +84,7 @@ chatDirectTests = do it "mark contact verified" testMarkContactVerified it "mark group member verified" testMarkGroupMemberVerified describe "message errors" $ do - xit "show message decryption error and update count" testMsgDecryptError + it "show message decryption error and update count" testMsgDecryptError describe "message reactions" $ do it "set message reactions" testSetMessageReactions diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 3319ef8486..978a7d9fc5 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -53,7 +53,7 @@ chatGroupTests = do it "group link member role" testGroupLinkMemberRole it "leaving and deleting the group joined via link should NOT delete previously existing direct contacts" testGroupLinkLeaveDelete describe "group message errors" $ do - xit "show message decryption error and update count" testGroupMsgDecryptError + it "show message decryption error and update count" testGroupMsgDecryptError describe "message reactions" $ do it "set group message reactions" testSetGroupMessageReactions