diff --git a/README.md b/README.md index 3d661a13ce..5551969ea5 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,10 @@ The channel through which you share the link does not have to be secure - it is After you connect, you can [verify connection security code](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md#connection-security-verification). +## User guide (NEW) + +Read about the app features and settings in the new [User guide](./docs/guide/README.md). + ## Help translating SimpleX Chat Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps, website and documents are translated to many other languages. @@ -175,13 +179,17 @@ You can use SimpleX with your own servers and still communicate with people usin Recent updates: -[Feb 04, 2023. v4.5 released - with multiple user profiles, message draft, transport isolation and Italian interface](./blog/20230204-simplex-chat-v4-5-user-chat-profiles.md). +[Mar 28, 2023. v4.6 released - with Android 8+ and ARMv7a support, hidden profiles, community moderation, improved audio/video calls and reduced battery usage](./blog/20230328-simplex-chat-v4-6-hidden-profiles.md). -[Jan 03, 2023. v4.4 released - with disappearing messages, "live" messages, connection security verifications, GIFs and stickers and with French interface language](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md). +[Mar 1, 2023. SimpleX File Transfer Protocol – send large files efficiently, privately and securely, soon to be integrated into SimpleX Chat apps.](./blog/20230301-simplex-file-transfer-protocol.md). -[Dec 06, 2022. November reviews and v4.3 released - with instant voice messages, irreversible deletion of sent messages and improved server configuration](./blog/20221206-simplex-chat-v4.3-voice-messages.md). +[Feb 4, 2023. v4.5 released - with multiple user profiles, message draft, transport isolation and Italian interface](./blog/20230204-simplex-chat-v4-5-user-chat-profiles.md). -[Nov 08, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). +[Jan 3, 2023. v4.4 released - with disappearing messages, "live" messages, connection security verifications, GIFs and stickers and with French interface language](./blog/20230103-simplex-chat-v4.4-disappearing-messages.md). + +[Dec 6, 2022. November reviews and v4.3 released - with instant voice messages, irreversible deletion of sent messages and improved server configuration](./blog/20221206-simplex-chat-v4.3-voice-messages.md). + +[Nov 8, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). [Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md). @@ -281,17 +289,21 @@ If you are considering developing with SimpleX platform please get in touch for - ✅ Multiple user profiles in the same chat database. - ✅ Optionally avoid re-using the same TCP session for multiple connections. - ✅ Preserve message drafts. -- 🏗 File server to optimize for efficient and private sending of large files. -- 🏗 Improved audio & video calls. +- ✅ File server to optimize for efficient and private sending of large files. +- ✅ Improved audio & video calls. +- ✅ Support older Android OS and 32-bit CPUs. +- ✅ Hidden chat profiles. +- 🏗 Sending and receiving large files via [XFTP protocol](./blog/20230301-simplex-file-transfer-protocol.md). +- 🏗 Video messages. - 🏗 SMP queue redundancy and rotation (manual is supported). - 🏗 Reduced battery and traffic usage in large groups. -- 🏗 Support older Android OS and 32-bit CPUs. +- Include optional message into connection request sent via contact address. - Ephemeral/disappearing/OTR conversations with the existing contacts. - Access password/pin (with optional alternative access password). - Local app files encryption. -- Video messages. - Improved navigation and search in the conversation (expand and scroll to quoted message, scroll to search results, etc.). - Message delivery confirmation (with sender opt-in or opt-out per contact, TBC). +- Privately share your location. - Feeds/broadcasts. - Web widgets for custom interactivity in the chats. - Programmable chat automations / rules (automatic replies/forward/deletion/sending, reminders, etc.). diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index ebc0c7e227..554a900b59 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -1440,6 +1440,8 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a chatItemSimpleUpdate(r.user, r.chatItem) is CR.SndFileProgressXFTP -> chatItemSimpleUpdate(r.user, r.chatItem) + is CR.SndFileCompleteXFTP -> + chatItemSimpleUpdate(r.user, r.chatItem) is CR.CallInvitation -> { chatModel.callManager.reportNewIncomingCall(r.callInvitation) } @@ -3077,6 +3079,7 @@ sealed class CR { @Serializable @SerialName("sndFileCancelled") class SndFileCancelled(val user: User, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta, val sndFileTransfers: List): CR() @Serializable @SerialName("sndFileRcvCancelled") class SndFileRcvCancelled(val user: User, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR() @Serializable @SerialName("sndFileProgressXFTP") class SndFileProgressXFTP(val user: User, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta, val sentSize: Long, val totalSize: Long): CR() + @Serializable @SerialName("sndFileCompleteXFTP") class SndFileCompleteXFTP(val user: User, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta): CR() @Serializable @SerialName("callInvitation") class CallInvitation(val callInvitation: RcvCallInvitation): CR() @Serializable @SerialName("callOffer") class CallOffer(val user: User, val contact: Contact, val callType: CallType, val offer: WebRTCSession, val sharedKey: String? = null, val askConfirmation: Boolean): CR() @Serializable @SerialName("callAnswer") class CallAnswer(val user: User, val contact: Contact, val answer: WebRTCSession): CR() @@ -3181,6 +3184,7 @@ sealed class CR { is SndFileRcvCancelled -> "sndFileRcvCancelled" is SndFileStart -> "sndFileStart" is SndFileProgressXFTP -> "sndFileProgressXFTP" + is SndFileCompleteXFTP -> "sndFileCompleteXFTP" is CallInvitation -> "callInvitation" is CallOffer -> "callOffer" is CallAnswer -> "callAnswer" @@ -3287,6 +3291,7 @@ sealed class CR { is SndFileRcvCancelled -> withUser(user, json.encodeToString(chatItem)) is SndFileStart -> withUser(user, json.encodeToString(chatItem)) is SndFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\nsentSize: $sentSize\ntotalSize: $totalSize") + is SndFileCompleteXFTP -> withUser(user, json.encodeToString(chatItem)) is CallInvitation -> "contact: ${callInvitation.contact.id}\ncallType: $callInvitation.callType\nsharedKey: ${callInvitation.sharedKey ?: ""}" is CallOffer -> withUser(user, "contact: ${contact.id}\ncallType: $callType\nsharedKey: ${sharedKey ?: ""}\naskConfirmation: $askConfirmation\noffer: ${json.encodeToString(offer)}") is CallAnswer -> withUser(user, "contact: ${contact.id}\nanswer: ${json.encodeToString(answer)}") diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index dc1a69e25f..54d78810f9 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1357,6 +1357,8 @@ func processReceivedMsg(_ res: ChatResponse) async { chatItemSimpleUpdate(user, aChatItem) case let .sndFileProgressXFTP(user, aChatItem, _, _, _): chatItemSimpleUpdate(user, aChatItem) + case let .sndFileCompleteXFTP(user, aChatItem, _): + chatItemSimpleUpdate(user, aChatItem) case let .callInvitation(invitation): m.callInvitations[invitation.contact.id] = invitation activateCall(invitation) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 48429538a3..22eea39a77 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -459,6 +459,7 @@ public enum ChatResponse: Decodable, Error { case sndFileCancelled(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) case sndFileRcvCancelled(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) case sndFileProgressXFTP(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileCompleteXFTP(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) case callInvitation(callInvitation: RcvCallInvitation) case callOffer(user: User, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) case callAnswer(user: User, contact: Contact, answer: WebRTCSession) @@ -568,6 +569,7 @@ public enum ChatResponse: Decodable, Error { case .sndFileCancelled: return "sndFileCancelled" case .sndFileRcvCancelled: return "sndFileRcvCancelled" case .sndFileProgressXFTP: return "sndFileProgressXFTP" + case .sndFileCompleteXFTP: return "sndFileCompleteXFTP" case .callInvitation: return "callInvitation" case .callOffer: return "callOffer" case .callAnswer: return "callAnswer" @@ -680,6 +682,7 @@ public enum ChatResponse: Decodable, Error { case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") + case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .callInvitation(inv): return String(describing: inv) case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))") case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))") diff --git a/blog/20230328-simplex-chat-v4-6-hidden-profiles.md b/blog/20230328-simplex-chat-v4-6-hidden-profiles.md index 839b1ef2f7..4fe282b081 100644 --- a/blog/20230328-simplex-chat-v4-6-hidden-profiles.md +++ b/blog/20230328-simplex-chat-v4-6-hidden-profiles.md @@ -16,7 +16,8 @@ permalink: "/blog/20230328-simplex-chat-v4-6-hidden-profiles.html" - [ARMv7a and Android 8+ support](#armv7a-32-bit-and-android-8-support) - [hidden chat profiles](#hidden-chat-profiles) -- [community moderation](#community-moderation) +- [group / community moderation](#group--community-moderation) +- [group welcome message](#group-welcome-message) - [improved audio/video calls](#improved-audiovideo-calls) - [reduced battery usage](#reduced-battery-usage). - [SMP server monitoring: status bot and page](#smp-server-monitoring) @@ -41,7 +42,7 @@ So instead of having an app password, that reduces convenience and doesn't impro It's important to remember that these hidden profiles are still stored locally on your device, so if somebody has access to your chat database (they need to know database passphrase, that is independent from device PIN or profile passphrases) or to chat console in the app, they will be able to access these profiles data and reset their passwords. We are considering how chat console can be better protected - e.g., by requiring a separate password or by providing an option to remove it from the UI permanently - tell us what you think. -### Community moderation +### Group / Community moderation Initially we did not design SimpleX Chat to support communities - our focus has always been maximum privacy and security. @@ -55,6 +56,10 @@ Secondly, group admins can now delete messages sent by other members (excluding These features will allow group owners to decide their own rules. More robust moderation tools are coming later when we build support for large communities. +### Group welcome message + +Group owners can now set a welcome message that will be shown to new members when they join the group via a group link. + ### Improved audio/video calls diff --git a/blog/README.md b/blog/README.md index 210cb1a871..01134bd1a6 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,6 +1,22 @@ # Blog -Feb 4, 2023 [SimpleX Chat v4.5 released](./20230103-simplex-chat-v4.4-disappearing-messages.md) +Mar 28, 2023 [SimpleX Chat v4.6 released](./20230328-simplex-chat-v4-6-hidden-profiles.md) + +- ARMv7a and Android 8+ support! +- hidden chat profiles +- group/community moderation +- group welcome message +- improved audio/video calls +- reduced battery usage +- SMP server monitoring: status bot and page + +Also, we added Chinese and Spanish language interface, thanks to our users and Weblate! + +Mar 1, 2023 [SimpleX File Transfer Protocol](./20230301-simplex-file-transfer-protocol.md) + +A new protocol for sending large files efficiently, privately and securely. It will be soon integrated into SimpleX Chat apps. + +Feb 4, 2023 [SimpleX Chat v4.5 released](./20230204-simplex-chat-v4-5-user-chat-profiles.md) - multiple chat profiles. - message draft. @@ -8,7 +24,7 @@ Feb 4, 2023 [SimpleX Chat v4.5 released](./20230103-simplex-chat-v4.4-disappeari - reduced battery usage. - private filenames. -Also, we added [Italian language interface](#french-language-interface), thanks to our users and Weblate! +Also, we added Italian language interface, thanks to our users and Weblate! Jan 3, 2023 [SimpleX Chat v4.4 released](./20230103-simplex-chat-v4.4-disappearing-messages.md) @@ -17,7 +33,7 @@ Jan 3, 2023 [SimpleX Chat v4.4 released](./20230103-simplex-chat-v4.4-disappeari - connection security verification. - animated images and stickers – now on iOS too. -Also, we added [French language interface](#french-language-interface), thanks to our users and Weblate! +Also, we added French language interface, thanks to our users and Weblate! Dec 6, 2022 [SimpleX Chat reviews and v4.3 released](./20221206-simplex-chat-v4.3-voice-messages.md) diff --git a/docs/guide/README.md b/docs/guide/README.md index b8674845b6..7836b0b406 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -1,4 +1,4 @@ -# SimpleX Chat user Guide +# SimpleX Chat User Guide The first messaging platform that has no user identifiers of any kind — 100% private by design. diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 774eb6895b..f90af36ae0 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2311,8 +2311,10 @@ processAgentMsgSndFile _corrId aFileId msg = (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do ms <- withStore' $ \db -> getGroupMembers db user g forM_ (zip rfds $ memberFTs ms) $ \mt -> sendToMember mt `catchError` (toView . CRChatError (Just user)) - -- TODO update database status and send event to view CRSndFileCompleteXFTP - pure () + ci' <- withStore $ \db -> do + liftIO $ updateCIFileStatus db user fileId CIFSSndComplete + getChatItemByFileId db user fileId + toView $ CRSndFileCompleteXFTP user ci' ft where memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts') @@ -2329,11 +2331,10 @@ processAgentMsgSndFile _corrId aFileId msg = _ -> pure () _ -> pure () -- TODO error? SFERR e -> do - throwChatError $ CEXFTPSndFile fileId (AgentSndFileId aFileId) e -- update chat item status -- send status to view -- agentXFTPDeleteSndFile - pure () + throwChatError $ CEXFTPSndFile fileId (AgentSndFileId aFileId) e where sendFileDescription :: SndFileTransfer -> ValidFileDescription 'FRecipient -> SharedMsgId -> (ChatMsgEvent 'Json -> m (SndMessage, Int64)) -> m Int64 sendFileDescription sft rfd msgId sendMsg = do @@ -2385,10 +2386,10 @@ processAgentMsgRcvFile _corrId aFileId msg = agentXFTPDeleteRcvFile user aFileId fileId toView $ CRRcvFileComplete user ci RFERR e -> do - throwChatError $ CEXFTPRcvFile fileId (AgentRcvFileId aFileId) e -- update chat item status -- send status to view agentXFTPDeleteRcvFile user aFileId fileId + throwChatError $ CEXFTPRcvFile fileId (AgentRcvFileId aFileId) e processAgentMessageConn :: forall m. ChatMonad m => User -> ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> m () processAgentMessageConn user _ agentConnId END = @@ -3292,13 +3293,17 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do checkSndInlineFTComplete :: Connection -> AgentMsgId -> m () checkSndInlineFTComplete conn agentMsgId = do - ft_ <- withStore' $ \db -> getSndFTViaMsgDelivery db user conn agentMsgId - forM_ ft_ $ \ft@SndFileTransfer {fileId} -> do - ci <- withStore $ \db -> do - liftIO $ updateSndFileStatus db ft FSComplete - liftIO $ deleteSndFileChunks db ft + sft_ <- withStore' $ \db -> getSndFTViaMsgDelivery db user conn agentMsgId + forM_ sft_ $ \sft@SndFileTransfer {fileId} -> do + ci@(AChatItem _ _ _ ChatItem {file}) <- withStore $ \db -> do + liftIO $ updateSndFileStatus db sft FSComplete + liftIO $ deleteSndFileChunks db sft updateDirectCIFileStatus db user fileId CIFSSndComplete - toView $ CRSndFileComplete user ci ft + case file of + Just CIFile {fileProtocol = FPXFTP} -> do + ft <- withStore $ \db -> getFileTransferMeta db user fileId + toView $ CRSndFileCompleteXFTP user ci ft + _ -> toView $ CRSndFileComplete user ci sft allowSendInline :: Integer -> Maybe InlineFileMode -> m Bool allowSendInline fileSize = \case diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 3104f1dd1f..d1be04e1e5 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -153,7 +153,7 @@ responseToView user_ ChatConfig {logLevel, testView} liveItems ts = \case CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft CRSndFileStartXFTP _ _ _ -> [] CRSndFileProgressXFTP _ _ _ _ _ -> [] - CRSndFileCompleteXFTP _ _ _ -> [] + CRSndFileCompleteXFTP u ci _ -> ttyUser u $ uploadedFile ci CRSndFileCancelledXFTP _ _ _ -> [] CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft] @@ -1064,6 +1064,13 @@ sendingFile_ :: StyledString -> SndFileTransfer -> [StyledString] sendingFile_ status ft@SndFileTransfer {recipientDisplayName = c} = [status <> " sending " <> sndFile ft <> " to " <> ttyContact c] +uploadedFile :: AChatItem -> [StyledString] +uploadedFile (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) = + ["uploaded " <> fileTransferStr fileId fileName <> " for " <> ttyContact c] +uploadedFile (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd}) = + ["uploaded " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] +uploadedFile _ = ["uploaded file"] -- shouldn't happen + sndFile :: SndFileTransfer -> StyledString sndFile SndFileTransfer {fileId, fileName} = fileTransferStr fileId fileName diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 98fe18e412..5887330e69 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -936,7 +936,7 @@ testXFTPFileTransfer = bob ##> "/fr 1 ./tests/tmp" bob <## "saving file 1 from alice to ./tests/tmp/test.pdf" -- alice <## "started sending file 1 (test.pdf) to bob" -- TODO "started uploading" ? - alice <## "completed sending file 1 (test.pdf) to bob" + alice <## "uploaded file 1 (test.pdf) for bob" bob <## "started receiving file 1 (test.pdf) from alice" bob <## "completed receiving file 1 (test.pdf) from alice" @@ -962,8 +962,8 @@ testXFTPGroupFileTransfer = cath <# "#team alice> sends file test.pdf (266.0 KiB / 272376 bytes)" cath <## "use /fr 1 [/ | ] to receive it" ] - -- alice <## "started sending file 1 (test.pdf) to bob" -- TODO "started uploading" ? - -- alice <## "completed sending file 1 (test.pdf) to bob" -- TODO "completed uploading" ? + -- alice <## "started sending file 1 (test.pdf) to #team" -- TODO "started uploading" ? + alice <## "uploaded file 1 (test.pdf) for #team" bob ##> "/fr 1 ./tests/tmp" bob @@ -1006,7 +1006,7 @@ testXFTPWithChangedConfig = bob ##> "/fr 1 ./tests/tmp" bob <## "saving file 1 from alice to ./tests/tmp/test.pdf" -- alice <## "started sending file 1 (test.pdf) to bob" -- TODO "started uploading" ? - alice <## "completed sending file 1 (test.pdf) to bob" + alice <## "uploaded file 1 (test.pdf) for bob" bob <## "started receiving file 1 (test.pdf) from alice" bob <## "completed receiving file 1 (test.pdf) from alice" @@ -1045,7 +1045,7 @@ testXFTPWithRelativePaths = bob ##> "/fr 1" bob <## "saving file 1 from alice to test.pdf" -- alice <## "started sending file 1 (test.pdf) to bob" -- TODO "started uploading" ? - alice <## "completed sending file 1 (test.pdf) to bob" + alice <## "uploaded file 1 (test.pdf) for bob" bob <## "started receiving file 1 (test.pdf) from alice" bob <## "completed receiving file 1 (test.pdf) from alice" @@ -1067,7 +1067,7 @@ testXFTPContinueRcv tmp = do bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" bob <## "use /fr 1 [/ | ] to receive it" -- alice <## "started sending file 1 (test.pdf) to bob" -- TODO "started uploading" ? - alice <## "completed sending file 1 (test.pdf) to bob" + alice <## "uploaded file 1 (test.pdf) for bob" -- server is down - file is not received withTestChatCfg tmp cfg "bob" $ \bob -> do