Merge pull request #401 from oxen-io/cleanup

Remove V1 OGS & File Server Support
This commit is contained in:
Niels Andriesse 2021-05-20 16:28:25 +10:00 committed by GitHub
commit 641a3f4d06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 87 additions and 2718 deletions

View File

@ -18,9 +18,6 @@
341341EF2187467A00192D59 /* ConversationViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 341341EE2187467900192D59 /* ConversationViewModel.m */; };
3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */; };
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430FE171F7751D4000EC51B /* GiphyAPI.swift */; };
34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; };
34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; };
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; };
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */; };
@ -30,9 +27,6 @@
34641E1F2088DA6D00E2EDE5 /* SAEScreenLockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34641E1E2088DA6D00E2EDE5 /* SAEScreenLockViewController.m */; };
34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; };
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
347850311FD7494A007B8332 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
347850321FD7494A007B8332 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
347850331FD7494A007B8332 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; };
3478504C1FD7496D007B8332 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; };
347850551FD749C0007B8332 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
3488F9362191CC4000E524CC /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3488F9352191CC4000E524CC /* MediaView.swift */; };
@ -184,7 +178,6 @@
B835247925C38D880089A44F /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B835247825C38D880089A44F /* MessageCell.swift */; };
B835249B25C3AB650089A44F /* VisibleMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B835249A25C3AB650089A44F /* VisibleMessageCell.swift */; };
B83524A525C3BA4B0089A44F /* InfoMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */; };
B83786802586D296003CE78E /* KeyPairMigrationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */; };
B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */; };
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; };
B849789625D4A2F500D0D0B3 /* LinkPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B849789525D4A2F500D0D0B3 /* LinkPreviewView.swift */; };
@ -198,7 +191,6 @@
B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AC225CB5D2900DBA3DB /* ConversationVC+Interaction.swift */; };
B8569AD325CBA13D00DBA3DB /* MediaTextOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AD225CBA13D00DBA3DB /* MediaTextOverlayView.swift */; };
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AE225CBB19A00DBA3DB /* DocumentView.swift */; };
B85A68B12587141A008CC492 /* Storage+Resetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85A68B02587141A008CC492 /* Storage+Resetting.swift */; };
B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; };
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
@ -241,7 +233,6 @@
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */; };
B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0742339EDCF00B4D94D /* NukeDataModal.swift */; };
B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */; };
B8A14D702589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */; };
B8AE75A425A6C6A6001A84D2 /* Data+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */; };
B8AE760B25ABFB5A001A84D2 /* GeneralUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B8AE760A25ABFB5A001A84D2 /* GeneralUtilities.m */; };
B8AE761425ABFBB9001A84D2 /* GeneralUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = B8AE760925ABFB00001A84D2 /* GeneralUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -254,7 +245,6 @@
B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B2C72563685C00551B4D /* CircleView.swift */; };
B8C2B332256376F000551B4D /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B331256376F000551B4D /* ThreadUtil.m */; };
B8C2B3442563782400551B4D /* ThreadUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = B8C2B33B2563770800551B4D /* ThreadUtil.h */; settings = {ATTRIBUTES = (Public, ); }; };
B8CADAE925AFADF400AAFA15 /* OpenGroupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AAFFCB25AE92150089E6DD /* OpenGroupManager.swift */; };
B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */; };
B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63623961D6D0091D419 /* NewDMVC.swift */; };
B8CCF63F23975CFB0091D419 /* JoinOpenGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63E23975CFB0091D419 /* JoinOpenGroupVC.swift */; };
@ -383,7 +373,6 @@
C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */; };
C32C5D9C256DD6DC003C73A2 /* OWSOutgoingReceiptManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB6F255A580F00E217F9 /* OWSOutgoingReceiptManager.m */; };
C32C5DA5256DD6E5003C73A2 /* OWSOutgoingReceiptManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDABD255A580100E217F9 /* OWSOutgoingReceiptManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
C32C5DBE256DD743003C73A2 /* OpenGroupPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA8C255A57FD00E217F9 /* OpenGroupPoller.swift */; };
C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB34255A580B00E217F9 /* ClosedGroupPoller.swift */; };
C32C5DC0256DD743003C73A2 /* Poller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB3A255A580B00E217F9 /* Poller.swift */; };
C32C5DC9256DD935003C73A2 /* ProxiedContentDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */; };
@ -678,15 +667,8 @@
C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */; };
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71F882558BA9F0043A11F /* Mnemonic.swift */; };
C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D662558A0170043A11F /* DiffieHellman.swift */; };
C3A721382558BDFA0043A11F /* OpenGroupMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A721342558BDF90043A11F /* OpenGroupMessage.swift */; };
C3A721392558BDFA0043A11F /* OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A721352558BDF90043A11F /* OpenGroupAPI.swift */; };
C3A7213A2558BDFA0043A11F /* OpenGroupInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A721362558BDFA0043A11F /* OpenGroupInfo.swift */; };
C3A7213B2558BDFA0043A11F /* OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A721372558BDFA0043A11F /* OpenGroup.swift */; };
C3A721902558C0CD0043A11F /* FileServerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A7218F2558C0CD0043A11F /* FileServerAPI.swift */; };
C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A721992558C1660043A11F /* AnyPromise+Conversion.swift */; };
C3A7222A2558C1E40043A11F /* DotNetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A722292558C1E40043A11F /* DotNetAPI.swift */; };
C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A7225D2558C38D0043A11F /* Promise+Retaining.swift */; };
C3A7229C2558E4310043A11F /* OpenGroupMessage+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A7229B2558E4310043A11F /* OpenGroupMessage+Conversion.swift */; };
C3A76A8D25DB83F90074CB90 /* PermissionMissingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A76A8C25DB83F90074CB90 /* PermissionMissingModal.swift */; };
C3AABDDF2553ECF00042FF4C /* Array+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Description.swift */; };
C3AAFFE825AE975D0089E6DD /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AAFFDE25AE96FF0089E6DD /* ConfigurationMessage+Convenience.swift */; };
@ -964,9 +946,6 @@
3427C64120F500DE00EEC730 /* OWSMessageTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageTimerView.h; sourceTree = "<group>"; };
3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageTimerView.m; sourceTree = "<group>"; };
3430FE171F7751D4000EC51B /* GiphyAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyAPI.swift; sourceTree = "<group>"; };
34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = "<group>"; };
34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "dripicons-v2.ttf"; sourceTree = "<group>"; };
34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ElegantIcons.ttf; sourceTree = "<group>"; };
34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = "<group>"; };
34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = "<group>"; };
34386A53207D271C009F5D9C /* NeverClearView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeverClearView.swift; sourceTree = "<group>"; };
@ -1179,7 +1158,6 @@
B835247825C38D880089A44F /* MessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = "<group>"; };
B835249A25C3AB650089A44F /* VisibleMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleMessageCell.swift; sourceTree = "<group>"; };
B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMessageCell.swift; sourceTree = "<group>"; };
B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairMigrationSheet.swift; sourceTree = "<group>"; };
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationButtonSet.swift; sourceTree = "<group>"; };
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = "<group>"; };
B84072952565E9F50037CB17 /* TSOutgoingMessage+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSOutgoingMessage+Conversion.swift"; sourceTree = "<group>"; };
@ -1196,7 +1174,6 @@
B8569AC225CB5D2900DBA3DB /* ConversationVC+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationVC+Interaction.swift"; sourceTree = "<group>"; };
B8569AD225CBA13D00DBA3DB /* MediaTextOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaTextOverlayView.swift; sourceTree = "<group>"; };
B8569AE225CBB19A00DBA3DB /* DocumentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentView.swift; sourceTree = "<group>"; };
B85A68B02587141A008CC492 /* Storage+Resetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Resetting.swift"; sourceTree = "<group>"; };
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
B87588582644CA9D000E60D0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -1216,7 +1193,6 @@
B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = "<group>"; };
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeDataModal.swift; sourceTree = "<group>"; };
B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = "<group>"; };
B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairMigrationSuccessSheet.swift; sourceTree = "<group>"; };
B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Trimming.swift"; sourceTree = "<group>"; };
B8AE760925ABFB00001A84D2 /* GeneralUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneralUtilities.h; sourceTree = "<group>"; };
B8AE760A25ABFB5A001A84D2 /* GeneralUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GeneralUtilities.m; sourceTree = "<group>"; };
@ -1329,7 +1305,6 @@
C33FDA87255A57FC00E217F9 /* TypingIndicators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicators.swift; sourceTree = "<group>"; };
C33FDA88255A57FD00E217F9 /* YapDatabaseTransaction+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "YapDatabaseTransaction+OWS.h"; sourceTree = "<group>"; };
C33FDA8B255A57FD00E217F9 /* AppVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppVersion.m; sourceTree = "<group>"; };
C33FDA8C255A57FD00E217F9 /* OpenGroupPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupPoller.swift; sourceTree = "<group>"; };
C33FDA8E255A57FD00E217F9 /* OWSFileSystem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSFileSystem.m; sourceTree = "<group>"; };
C33FDA90255A57FD00E217F9 /* TSYapDatabaseObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSYapDatabaseObject.m; sourceTree = "<group>"; };
C33FDA96255A57FE00E217F9 /* OWSDispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDispatch.h; sourceTree = "<group>"; };
@ -1683,18 +1658,10 @@
C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+messagePadding.h"; sourceTree = "<group>"; };
C3A71D662558A0170043A11F /* DiffieHellman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffieHellman.swift; sourceTree = "<group>"; };
C3A71F882558BA9F0043A11F /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = "<group>"; };
C3A721342558BDF90043A11F /* OpenGroupMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupMessage.swift; sourceTree = "<group>"; };
C3A721352558BDF90043A11F /* OpenGroupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupAPI.swift; sourceTree = "<group>"; };
C3A721362558BDFA0043A11F /* OpenGroupInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupInfo.swift; sourceTree = "<group>"; };
C3A721372558BDFA0043A11F /* OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroup.swift; sourceTree = "<group>"; };
C3A7218F2558C0CD0043A11F /* FileServerAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileServerAPI.swift; sourceTree = "<group>"; };
C3A721992558C1660043A11F /* AnyPromise+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyPromise+Conversion.swift"; sourceTree = "<group>"; };
C3A722292558C1E40043A11F /* DotNetAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DotNetAPI.swift; sourceTree = "<group>"; };
C3A7225D2558C38D0043A11F /* Promise+Retaining.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Retaining.swift"; sourceTree = "<group>"; };
C3A7229B2558E4310043A11F /* OpenGroupMessage+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupMessage+Conversion.swift"; sourceTree = "<group>"; };
C3A76A8C25DB83F90074CB90 /* PermissionMissingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionMissingModal.swift; sourceTree = "<group>"; };
C3AA6BB824CE8F1B002358B6 /* Migrating Translations from Android.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = "Migrating Translations from Android.md"; path = "Meta/Translations/Migrating Translations from Android.md"; sourceTree = "<group>"; };
C3AAFFCB25AE92150089E6DD /* OpenGroupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManager.swift; sourceTree = "<group>"; };
C3AAFFDE25AE96FF0089E6DD /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
C3AAFFF125AE99710089E6DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C3ADC66026426688005F1414 /* ShareVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareVC.swift; sourceTree = "<group>"; };
@ -1967,9 +1934,6 @@
34330A581E7875FB00DF2FB9 /* Fonts */ = {
isa = PBXGroup;
children = (
34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */,
34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */,
34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */,
B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */,
C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */,
);
@ -2467,31 +2431,6 @@
path = Meta;
sourceTree = "<group>";
};
C3227FF4260AAD58006EA627 /* V2 */ = {
isa = PBXGroup;
children = (
C3DB6694260AC923001EFC55 /* OpenGroupV2.swift */,
B88FA7B726045D100049422F /* OpenGroupAPIV2.swift */,
C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */,
C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */,
C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */,
);
path = V2;
sourceTree = "<group>";
};
C3228005260AAD7E006EA627 /* V1 */ = {
isa = PBXGroup;
children = (
C3A721372558BDFA0043A11F /* OpenGroup.swift */,
C3A721352558BDF90043A11F /* OpenGroupAPI.swift */,
C3A721362558BDFA0043A11F /* OpenGroupInfo.swift */,
C3AAFFCB25AE92150089E6DD /* OpenGroupManager.swift */,
C3A721342558BDF90043A11F /* OpenGroupMessage.swift */,
C3A7229B2558E4310043A11F /* OpenGroupMessage+Conversion.swift */,
);
path = V1;
sourceTree = "<group>";
};
C328252E25CA54F70062D0A7 /* Context Menu */ = {
isa = PBXGroup;
children = (
@ -2539,7 +2478,6 @@
isa = PBXGroup;
children = (
C33FDB34255A580B00E217F9 /* ClosedGroupPoller.swift */,
C33FDA8C255A57FD00E217F9 /* OpenGroupPoller.swift */,
C3DB66C2260ACCE6001EFC55 /* OpenGroupPollerV2.swift */,
C33FDB3A255A580B00E217F9 /* Poller.swift */,
);
@ -2674,14 +2612,6 @@
path = Mentions;
sourceTree = "<group>";
};
C32C5D49256DD522003C73A2 /* Database */ = {
isa = PBXGroup;
children = (
B85A68B02587141A008CC492 /* Storage+Resetting.swift */,
);
path = Database;
sourceTree = "<group>";
};
C331FF1C2558F9D300070591 /* SessionUIKit */ = {
isa = PBXGroup;
children = (
@ -2868,8 +2798,6 @@
C36096AF25AD1932008B62B2 /* Sheets & Modals */ = {
isa = PBXGroup;
children = (
B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */,
B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */,
B86BD08323399ACF000F5AE3 /* Modal.swift */,
C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */,
);
@ -3150,8 +3078,11 @@
C3A721332558BDDF0043A11F /* Open Groups */ = {
isa = PBXGroup;
children = (
C3228005260AAD7E006EA627 /* V1 */,
C3227FF4260AAD58006EA627 /* V2 */,
C3DB6694260AC923001EFC55 /* OpenGroupV2.swift */,
B88FA7B726045D100049422F /* OpenGroupAPIV2.swift */,
C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */,
C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */,
C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */,
);
path = "Open Groups";
sourceTree = "<group>";
@ -3159,7 +3090,6 @@
C3A7215C2558C0AC0043A11F /* File Server */ = {
isa = PBXGroup;
children = (
C3A7218F2558C0CD0043A11F /* FileServerAPI.swift */,
B87EF17026367CF800124B3C /* FileServerAPIV2.swift */,
);
path = "File Server";
@ -3171,7 +3101,6 @@
C33FDB01255A580700E217F9 /* AppReadiness.h */,
C33FDB75255A581000E217F9 /* AppReadiness.m */,
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
C3A722292558C1E40043A11F /* DotNetAPI.swift */,
C37F53E8255BA9BB002AEA92 /* Environment.h */,
C37F5402255BA9ED002AEA92 /* Environment.m */,
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
@ -3451,11 +3380,11 @@
C3F0A58F255C8E3D007BE2A3 /* Meta */ = {
isa = PBXGroup;
children = (
B81D260326158DF5004D1FE1 /* Certificates */,
76EB03C218170B33006006FC /* AppDelegate.h */,
76EB03C318170B33006006FC /* AppDelegate.m */,
C3AAFFF125AE99710089E6DD /* AppDelegate.swift */,
34D99CE3217509C1000AFB39 /* AppEnvironment.swift */,
B81D260326158DF5004D1FE1 /* Certificates */,
B8FF8E6025C10D8B004D1F22 /* Countries */,
34330A581E7875FB00DF2FB9 /* Fonts */,
B66DBF4919D5BBC8006EA940 /* Images.xcassets */,
@ -3571,7 +3500,6 @@
C36096BC25AD1C3E008B62B2 /* Backups */,
C360969C25AD18BA008B62B2 /* Closed Groups */,
B835246C25C38AA20089A44F /* Conversations */,
C32C5D49256DD522003C73A2 /* Database */,
C32B405424A961E1001117B5 /* Dependencies */,
C36096A525AD18D7008B62B2 /* DMs */,
C360968E25AD16E8008B62B2 /* Home */,
@ -4067,12 +3995,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
347850321FD7494A007B8332 /* ElegantIcons.ttf in Resources */,
4535186E1FC635DD00210559 /* MainInterface.storyboard in Resources */,
347850551FD749C0007B8332 /* Localizable.strings in Resources */,
347850331FD7494A007B8332 /* fontawesome-webfont.ttf in Resources */,
3478504C1FD7496D007B8332 /* Images.xcassets in Resources */,
347850311FD7494A007B8332 /* dripicons-v2.ttf in Resources */,
C3D90A1125773888002C9DF5 /* Colors.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -4131,9 +4056,7 @@
4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */,
4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */,
34CF078A203E6B78005C4D61 /* end_call_tone_cept.caf in Resources */,
34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */,
A5509ECA1A69AB8B00ABA4BC /* Main.storyboard in Resources */,
34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */,
C3CA3AA2255CDADA00F4C6D4 /* english.txt in Resources */,
B6F509971AA53F760068F56A /* Localizable.strings in Resources */,
C3A01E05261D24C400290BEB /* public-loki-foundation.der in Resources */,
@ -4146,7 +4069,6 @@
C3CA3AB4255CDAE600F4C6D4 /* japanese.txt in Resources */,
B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */,
34CF0787203E6B78005C4D61 /* busy_tone_ansi.caf in Resources */,
34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */,
45A2F005204473A3002E978A /* NewMessage.aifc in Resources */,
45B74A882044AAB600CD42F8 /* aurora.aifc in Resources */,
45B74A742044AAB600CD42F8 /* aurora-quiet.aifc in Resources */,
@ -4684,8 +4606,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C3A7229C2558E4310043A11F /* OpenGroupMessage+Conversion.swift in Sources */,
C32C5DBE256DD743003C73A2 /* OpenGroupPoller.swift in Sources */,
B8856D08256F10F1001CE70E /* DeviceSleepManager.swift in Sources */,
C3471F4C25553AB000297E91 /* MessageReceiver+Decryption.swift in Sources */,
C300A5D32554B05A00555489 /* TypingIndicator.swift in Sources */,
@ -4710,10 +4630,8 @@
C300A5FC2554B0A000555489 /* MessageReceiver.swift in Sources */,
C32C5A76256DBBCF003C73A2 /* SignalAttachment.swift in Sources */,
C32C5CA4256DD1DC003C73A2 /* TSAccountManager.m in Sources */,
C3A7213B2558BDFA0043A11F /* OpenGroup.swift in Sources */,
C352A3892557876500338F3E /* JobQueue.swift in Sources */,
C3BBE0B52554F0E10050F1E3 /* ProofOfWork.swift in Sources */,
C3A721902558C0CD0043A11F /* FileServerAPI.swift in Sources */,
C32C59C1256DB41F003C73A2 /* TSGroupThread.m in Sources */,
C3A3A08F256E1728004D228D /* FullTextSearchFinder.swift in Sources */,
B8856D1A256F114D001CE70E /* ProximityMonitoringManager.swift in Sources */,
@ -4734,12 +4652,10 @@
C32C5DC0256DD743003C73A2 /* Poller.swift in Sources */,
C3C2A7682553A3D900C340D1 /* VisibleMessage+Contact.swift in Sources */,
C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */,
C3A721392558BDFA0043A11F /* OpenGroupAPI.swift in Sources */,
C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */,
B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */,
C32C5D24256DD4C0003C73A2 /* MentionsManager.swift in Sources */,
C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */,
C3A721382558BDFA0043A11F /* OpenGroupMessage.swift in Sources */,
C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */,
B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */,
C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */,
@ -4759,7 +4675,6 @@
C32C5EDC256DF501003C73A2 /* YapDatabaseConnection+OWS.m in Sources */,
C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */,
B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */,
B8CADAE925AFADF400AAFA15 /* OpenGroupManager.swift in Sources */,
B8856D69256F141F001CE70E /* OWSWindowManager.m in Sources */,
C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */,
C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */,
@ -4773,7 +4688,6 @@
C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */,
C3DB66CC260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift in Sources */,
C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */,
C3A7222A2558C1E40043A11F /* DotNetAPI.swift in Sources */,
C34A977425A3E34A00852C71 /* ClosedGroupControlMessage.swift in Sources */,
B88FA7B826045D100049422F /* OpenGroupAPIV2.swift in Sources */,
C32C5E97256DE0CB003C73A2 /* OWSPrimaryStorage.m in Sources */,
@ -4807,7 +4721,6 @@
C32C5F11256DF79A003C73A2 /* SSKIncrementingIdFinder.swift in Sources */,
C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */,
C32C5EEE256DF54E003C73A2 /* TSDatabaseView.m in Sources */,
C3A7213A2558BDFA0043A11F /* OpenGroupInfo.swift in Sources */,
C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */,
C3A3A13C256E1B27004D228D /* OWSMediaGalleryFinder.m in Sources */,
C32C5AAE256DBE8F003C73A2 /* TSInteraction.m in Sources */,
@ -4871,7 +4784,6 @@
34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */,
34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */,
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */,
B85A68B12587141A008CC492 /* Storage+Resetting.swift in Sources */,
3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */,
C3A76A8D25DB83F90074CB90 /* PermissionMissingModal.swift in Sources */,
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */,
@ -4951,7 +4863,6 @@
B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */,
3488F9362191CC4000E524CC /* MediaView.swift in Sources */,
B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */,
B8A14D702589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift in Sources */,
3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */,
C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */,
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */,
@ -4971,7 +4882,6 @@
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */,
B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */,
B83786802586D296003CE78E /* KeyPairMigrationSheet.swift in Sources */,
B821494F25D4E163009C0F2A /* BodyTextView.swift in Sources */,
C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */,
B835249B25C3AB650089A44F /* VisibleMessageCell.swift in Sources */,

View File

@ -536,11 +536,8 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
let threadID = thread.uniqueId!
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
let publicKey = message.authorId
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: threadID) {
OpenGroupAPIV2.ban(publicKey, from: openGroupV2.room, on: openGroupV2.server).retainUntilComplete()
} else if let openGroup = Storage.shared.getOpenGroup(for: threadID) {
OpenGroupAPI.ban(publicKey, from: openGroup.server).retainUntilComplete()
}
guard let openGroupV2 = Storage.shared.getV2OpenGroup(for: threadID) else { return }
OpenGroupAPIV2.ban(publicKey, from: openGroupV2.room, on: openGroupV2.server).retainUntilComplete()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
present(alert, animated: true, completion: nil)

View File

@ -988,7 +988,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (!message.isOpenGroupMessage) return;
// Get the open group
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
SNOpenGroupV2 *openGroupV2 = [LKStorage.shared getV2OpenGroupForThreadID:groupThread.uniqueId];
if (openGroup == nil && openGroupV2 == nil) return;
@ -997,8 +996,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
if (openGroupV2 != nil) {
if (![SNOpenGroupAPIV2 isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
} else if (openGroup != nil) {
if (![SNOpenGroupAPI isUserModerator:userPublicKey forChannel:openGroup.channel onServer:openGroup.server]) { return; }
}
}
@ -1009,11 +1006,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
// Roll back
[self.interaction save];
}) retainUntilComplete];
} else if (openGroup != nil) {
[[SNOpenGroupAPI deleteMessageWithID:message.openGroupServerMessageID forGroup:openGroup.channel onServer:openGroup.server isSentByUser:wasSentByUser].catch(^(NSError *error) {
// Roll back
[self.interaction save];
}) retainUntilComplete];
}
}
}
@ -1070,16 +1062,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (!message.isOpenGroupMessage) return true;
// Ensure we have the details needed to contact the server
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
SNOpenGroupV2 *openGroupV2 = [LKStorage.shared getV2OpenGroupForThreadID:groupThread.uniqueId];
if (openGroup == nil && openGroupV2 == nil) return true;
if (openGroupV2 == nil) return true;
if (interationType == OWSInteractionType_IncomingMessage) {
// Only allow deletion on incoming messages if the user has moderation permission
if (openGroupV2 != nil) {
return [SNOpenGroupAPIV2 isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
} else {
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:openGroup.channel onServer:openGroup.server];
}
} else {
return YES;
@ -1096,15 +1085,12 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (!message.isOpenGroupMessage) return false;
// Ensure we have the details needed to contact the server
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
SNOpenGroupV2 *openGroupV2 = [LKStorage.shared getV2OpenGroupForThreadID:groupThread.uniqueId];
if (openGroup == nil && openGroupV2 == nil) return false;
if (openGroupV2 == nil) return false;
// Check that we're a moderator
if (openGroupV2 != nil) {
return [SNOpenGroupAPIV2 isUserModerator:[SNGeneralUtilities getUserPublicKey] forRoom:openGroupV2.room onServer:openGroupV2.server];
} else {
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:openGroup.channel onServer:openGroup.server];
}
}

View File

@ -312,9 +312,6 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate,
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: thread.uniqueId!) {
mentionsView.openGroupServer = openGroupV2.server
mentionsView.openGroupRoom = openGroupV2.room
} else if let openGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!) {
mentionsView.openGroupServer = openGroup.server
mentionsView.openGroupChannel = openGroup.channel
}
mentionsView.candidates = candidates
let mentionCellHeight = Values.smallProfilePictureSize + 2 * Values.smallSpacing

View File

@ -165,9 +165,6 @@ private extension MentionSelectionView {
if let server = openGroupServer, let room = openGroupRoom {
let isUserModerator = OpenGroupAPIV2.isUserModerator(mentionCandidate.publicKey, for: room, on: server)
moderatorIconImageView.isHidden = !isUserModerator
} else if let server = openGroupServer, let channel = openGroupChannel {
let isUserModerator = OpenGroupAPI.isUserModerator(mentionCandidate.publicKey, for: channel, on: server)
moderatorIconImageView.isHidden = !isUserModerator
} else {
moderatorIconImageView.isHidden = true
}

View File

@ -222,9 +222,6 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: thread.uniqueId!) {
let isUserModerator = OpenGroupAPIV2.isUserModerator(senderSessionID, for: openGroupV2.room, on: openGroupV2.server)
moderatorIconImageView.isHidden = !isUserModerator || profilePictureView.isHidden
} else if let openGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!) {
let isUserModerator = OpenGroupAPI.isUserModerator(senderSessionID, for: openGroup.channel, on: openGroup.server)
moderatorIconImageView.isHidden = !isUserModerator || profilePictureView.isHidden
} else {
moderatorIconImageView.isHidden = true
}

View File

@ -100,11 +100,8 @@ final class ConversationTitleView : UIView {
switch thread.groupModel.groupType {
case .closedGroup: userCount = UInt64(thread.groupModel.groupMemberIds.count)
case .openGroup:
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: self.thread.uniqueId!) {
userCount = Storage.shared.getUserCount(forV2OpenGroupWithID: openGroupV2.id)
} else if let openGroup = Storage.shared.getOpenGroup(for: self.thread.uniqueId!) {
userCount = given(Storage.shared.getUserCount(forOpenGroupWithID: openGroup.id)) { UInt64($0) }
}
guard let openGroupV2 = Storage.shared.getV2OpenGroup(for: self.thread.uniqueId!) else { return nil }
userCount = Storage.shared.getUserCount(forV2OpenGroupWithID: openGroupV2.id)
default: break
}
if let userCount = userCount {

View File

@ -63,7 +63,7 @@ final class JoinOpenGroupModal : Modal {
// MARK: Interaction
@objc private func joinOpenGroup() {
guard let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: url), Features.useV2OpenGroups else {
guard let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: url) else {
let alert = UIAlertController(title: "Couldn't Join", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
return presentingViewController!.present(alert, animated: true, completion: nil)

View File

@ -1,42 +0,0 @@
import PromiseKit
extension Storage {
static func prepareForV2KeyPairMigration() {
let userDefaults = UserDefaults.standard
let isUsingAPNs = userDefaults[.isUsingFullAPNs]
if isUsingAPNs, let hexEncodedToken = userDefaults[.deviceToken] {
let token = Data(hex: hexEncodedToken)
PushNotificationAPI.unregister(token).retainUntilComplete() // TODO: Wait for this to complete?
}
let name = Storage.shared.getUser()!.name!
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.stopPoller()
appDelegate.stopClosedGroupPoller()
appDelegate.stopOpenGroupPollers()
OWSStorage.resetAllStorage()
OWSUserProfile.resetProfileStorage()
Environment.shared.preferences.clear()
AppEnvironment.shared.notificationPresenter.clearAllNotifications()
userDefaults[.isUsingFullAPNs] = isUsingAPNs
userDefaults[.displayName] = name
userDefaults[.isMigratingToV2KeyPair] = true
exit(0)
}
static func finishV2KeyPairMigration(navigationController: UINavigationController) {
let seed = Data.getSecureRandomData(ofSize: 16)!
let (ed25519KeyPair, x25519KeyPair) = KeyPairUtilities.generate(from: seed)
KeyPairUtilities.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519KeyPair.hexEncodedPublicKey
UserDefaults.standard[.hasViewedSeed] = false
let displayName = UserDefaults.standard[.displayName]! // Checked earlier
OWSProfileManager.shared().updateLocalProfileName(displayName, avatarImage: nil, success: { }, failure: { _ in }, requiresSync: false)
TSAccountManager.sharedInstance().didRegister()
let homeVC = HomeVC()
navigationController.setViewControllers([ homeVC ], animated: true)
let syncTokensJob = SyncPushTokensJob(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences)
syncTokensJob.uploadOnlyIfStale = false
let _: Promise<Void> = syncTokensJob.run()
}
}

View File

@ -156,9 +156,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
let _ = IP2Country.shared.populateCacheIfNeeded()
}
// Get default open group rooms if needed
if Features.useV2OpenGroups {
OpenGroupAPIV2.getDefaultRoomsIfNeeded()
}
OpenGroupAPIV2.getDefaultRoomsIfNeeded()
}
override func viewDidAppear(_ animated: Bool) {
@ -380,13 +378,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
private func delete(_ thread: TSThread) {
let openGroupV2 = Storage.shared.getV2OpenGroup(for: thread.uniqueId!)
let openGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!)
Storage.write { transaction in
Storage.shared.cancelPendingMessageSendJobs(for: thread.uniqueId!, using: transaction)
if let openGroupV2 = openGroupV2 {
OpenGroupManagerV2.shared.delete(openGroupV2, associatedWith: thread, using: transaction)
} else if let openGroup = openGroup {
OpenGroupManager.shared.delete(openGroup, associatedWith: thread, using: transaction)
} else if let thread = thread as? TSGroupThread, thread.isClosedGroup == true {
let groupID = thread.groupModel.groupId
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)

View File

@ -151,10 +151,9 @@ static NSTimeInterval launchStartedAt;
[LKAppModeManager configureWithDelegate:self];
// OWSLinkPreview and OpenGroup are now in SessionMessagingKit, so to still be able to deserialize them we
// OWSLinkPreview is now in SessionMessagingKit, so to still be able to deserialize them we
// need to tell NSKeyedUnarchiver about the changes.
[NSKeyedUnarchiver setClass:OWSLinkPreview.class forClassName:@"SessionServiceKit.OWSLinkPreview"];
[NSKeyedUnarchiver setClass:SNOpenGroup.class forClassName:@"LKPublicChat"];
BOOL isLoggingEnabled;
#ifdef DEBUG
@ -406,7 +405,7 @@ static NSTimeInterval launchStartedAt;
} requiresSync:YES];
}
if (CurrentAppContext().isMainApp && SNFeatures.useV2OpenGroups) {
if (CurrentAppContext().isMainApp) {
[SNOpenGroupAPIV2 getDefaultRoomsIfNeeded];
}
@ -728,12 +727,10 @@ static NSTimeInterval launchStartedAt;
- (void)startOpenGroupPollersIfNeeded
{
[SNOpenGroupManager.shared startPolling];
[SNOpenGroupManagerV2.shared startPolling];
}
- (void)stopOpenGroupPollers {
[SNOpenGroupManager.shared stopPolling];
[SNOpenGroupManagerV2.shared stopPolling];
}

Binary file not shown.

Binary file not shown.

View File

@ -82,13 +82,6 @@ final class LandingVC : BaseVC {
view.addSubview(mainStackView)
mainStackView.pin(to: view)
topSpacer.heightAnchor.constraint(equalTo: bottomSpacer.heightAnchor, multiplier: 1).isActive = true
// Auto-migrate if needed
let userDefaults = UserDefaults.standard
if userDefaults[.isMigratingToV2KeyPair] {
if userDefaults[.displayName] != nil {
Storage.finishV2KeyPairMigration(navigationController: navigationController!)
}
}
}
// MARK: Interaction

View File

@ -125,19 +125,10 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
}
fileprivate func joinOpenGroup(with string: String) {
// A V1 open group URL will look like: https:// + <host>
// A V2 open group URL will look like: <optional scheme> + <host> + <optional port> + <room> + <public key>
// The host doesn't parse if no explicit scheme is provided
if let url = URL(string: string), let host = url.host ?? given(string.split(separator: "/").first, { String($0) }) {
if let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: string), Features.useV2OpenGroups {
joinV2OpenGroup(room: room, server: server, publicKey: publicKey)
} else {
// Inputs that should work:
// loki.opensession.id
// https://loki.opensession.id
// http://loki.opensession.id (still goes to HTTPS)
joinV1OpenGroup("https://" + host)
}
if let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: string) {
joinV2OpenGroup(room: room, server: server, publicKey: publicKey)
} else {
let title = NSLocalizedString("invalid_url", comment: "")
let message = "Please check the URL you entered and try again."
@ -145,32 +136,6 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
}
}
private func joinV1OpenGroup(_ string: String) {
guard !isJoining else { return }
isJoining = true
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in
Storage.shared.write { transaction in
OpenGroupManager.shared.add(with: string, using: transaction)
.done(on: DispatchQueue.main) { [weak self] _ in
self?.presentingViewController!.dismiss(animated: true, completion: nil)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...)
}
.catch(on: DispatchQueue.main) { [weak self] error in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
var title = "Couldn't Join"
var message = ""
if case OnionRequestAPI.Error.httpRequestFailedAtDestination(let statusCode, _) = error, statusCode == 401 || statusCode == 403 {
title = "Unauthorized"
message = "Please ask the open group operator to add you to the group."
}
self?.isJoining = false
self?.showError(title: title, message: message)
}
}
}
}
fileprivate func joinV2OpenGroup(room: String, server: String, publicKey: String) {
guard !isJoining else { return }
isJoining = true
@ -242,13 +207,8 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate,
nextButtonContainer.pin(.trailing, to: .trailing, of: nextButton, withInset: 80)
nextButtonContainer.pin(.bottom, to: .bottom, of: nextButton)
// Stack view
let stackView: UIStackView
if Features.useV2OpenGroups {
stackView = UIStackView(arrangedSubviews: [ urlTextView, UIView.spacer(withHeight: Values.mediumSpacing), suggestionGridTitleLabel,
UIView.spacer(withHeight: Values.mediumSpacing), suggestionGrid, UIView.vStretchingSpacer(), nextButtonContainer ])
} else {
stackView = UIStackView(arrangedSubviews: [ urlTextView, UIView.vStretchingSpacer(), nextButtonContainer ])
}
let stackView = UIStackView(arrangedSubviews: [ urlTextView, UIView.spacer(withHeight: Values.mediumSpacing), suggestionGridTitleLabel,
UIView.spacer(withHeight: Values.mediumSpacing), suggestionGrid, UIView.vStretchingSpacer(), nextButtonContainer ])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(uniform: Values.largeSpacing)

View File

@ -204,26 +204,17 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
button.set(.height, to: SettingsVC.buttonHeight)
return button
}
var result = [
return [
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_privacy_button_title", comment: ""), color: Colors.text, action: #selector(showPrivacySettings)),
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_notifications_button_title", comment: ""), color: Colors.text, action: #selector(showNotificationSettings)),
getSeparator()
]
if !KeyPairUtilities.hasV2KeyPair() {
result += [
getSettingButton(withTitle: "Upgrade Session ID", color: Colors.text, action: #selector(upgradeSessionID)),
getSeparator()
]
}
result += [
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_recovery_phrase_button_title", comment: ""), color: Colors.text, action: #selector(showSeed)),
getSeparator(),
getSettingButton(withTitle: NSLocalizedString("vc_settings_clear_all_data_button_title", comment: ""), color: Colors.destructive, action: #selector(clearAllData)),
getSeparator()
]
return result
}
// MARK: General
@ -346,7 +337,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
DispatchQueue.main.async {
modalActivityIndicator.dismiss {
var isMaxFileSizeExceeded = false
if let error = error as? DotNetAPI.Error {
if let error = error as? FileServerAPIV2.Error {
isMaxFileSizeExceeded = (error == .maxFileSizeExceeded)
}
let title = isMaxFileSizeExceeded ? "Maximum File Size Exceeded" : "Couldn't Update Profile"
@ -458,16 +449,6 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
UIApplication.shared.open(url)
}
@objc private func upgradeSessionID() {
let message = "Youre upgrading to a new Session ID. This will give you improved privacy and security, but it will clear ALL app data. Contacts and conversations will be lost. Proceed?"
let alert = UIAlertController(title: "Upgrade Session ID?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in
Storage.prepareForV2KeyPairMigration()
})
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
@objc private func showSeed() {
let seedModal = SeedModal()
seedModal.modalPresentationStyle = .overFullScreen

View File

@ -1,75 +0,0 @@
final class KeyPairMigrationSheet : Sheet {
override class var isDismissable: Bool { false }
override func populateContentView() {
// Image view
let imageView = UIImageView(image: #imageLiteral(resourceName: "Shield").withTint(Colors.text))
imageView.set(.width, to: 64)
imageView.set(.height, to: 64)
imageView.contentMode = .scaleAspectFit
// Title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = "Session IDs Just Got Better"
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
// Top stack view
let topStackView = UIStackView(arrangedSubviews: [ imageView, titleLabel ])
topStackView.axis = .vertical
topStackView.spacing = Values.largeSpacing
topStackView.alignment = .center
// Explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.textAlignment = .center
explanationLabel.text = """
Weve upgraded Session IDs to make them even more private and secure. To ensure your continued privacy you're now required to upgrade.
Your existing contacts and conversations will be lost, but youll be able to use Session knowing you have the best privacy and security possible.
"""
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
// Upgrade now button
let upgradeNowButton = Button(style: .prominentOutline, size: .large)
upgradeNowButton.set(.width, to: 240)
upgradeNowButton.setTitle("Upgrade Now", for: UIControl.State.normal)
upgradeNowButton.addTarget(self, action: #selector(upgradeNow), for: UIControl.Event.touchUpInside)
// Upgrade now button
let upgradeLaterButton = Button(style: .prominentOutline, size: .large)
upgradeLaterButton.set(.width, to: 240)
upgradeLaterButton.setTitle("Upgrade Later", for: UIControl.State.normal)
upgradeLaterButton.addTarget(self, action: #selector(upgradeLater), for: UIControl.Event.touchUpInside)
// Button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ upgradeNowButton, upgradeLaterButton ])
buttonStackView.axis = .vertical
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.alignment = .center
// Main stack view
let stackView = UIStackView(arrangedSubviews: [ topStackView, explanationLabel, buttonStackView ])
stackView.axis = .vertical
stackView.spacing = Values.veryLargeSpacing
stackView.alignment = .center
// Constraints
contentView.addSubview(stackView)
stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.veryLargeSpacing)
stackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.veryLargeSpacing)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.veryLargeSpacing + overshoot)
}
@objc private func upgradeNow() {
Storage.prepareForV2KeyPairMigration()
}
@objc private func upgradeLater() {
let alert = UIAlertController(title: "Warning", message: "You won't be able to send or receive messages until you upgrade.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", accessibilityIdentifier: nil, style: .default, handler: nil))
presentingViewController?.dismiss(animated: true, completion: nil) // Dismiss self
presentingViewController?.present(alert, animated: true, completion: nil)
}
}

View File

@ -1,94 +0,0 @@
final class KeyPairMigrationSuccessSheet : Sheet {
private lazy var sessionIDLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = Fonts.spaceMono(ofSize: isIPhone5OrSmaller ? Values.mediumFontSize : 20)
result.numberOfLines = 0
result.lineBreakMode = .byCharWrapping
return result
}()
private lazy var copyButton: Button = {
let result = Button(style: .prominentOutline, size: .large)
result.set(.width, to: 240)
result.setTitle(NSLocalizedString("copy", comment: ""), for: UIControl.State.normal)
result.addTarget(self, action: #selector(copySessionID), for: UIControl.Event.touchUpInside)
return result
}()
override func populateContentView() {
// Image view
let imageView = UIImageView(image: #imageLiteral(resourceName: "Shield").withTint(Colors.text))
imageView.set(.width, to: 64)
imageView.set(.height, to: 64)
imageView.contentMode = .scaleAspectFit
// Title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = "Upgrade Successful!"
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
// Top stack view
let topStackView = UIStackView(arrangedSubviews: [ imageView, titleLabel ])
topStackView.axis = .vertical
topStackView.spacing = Values.largeSpacing
topStackView.alignment = .center
// Explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.textAlignment = .center
explanationLabel.text = "Your new and improved Session ID is:"
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
// Session ID label
sessionIDLabel.text = getUserHexEncodedPublicKey()
// Session ID container
let sessionIDContainer = UIView()
sessionIDContainer.addSubview(sessionIDLabel)
sessionIDLabel.pin(to: sessionIDContainer, withInset: Values.mediumSpacing)
sessionIDContainer.layer.cornerRadius = TextField.cornerRadius
sessionIDContainer.layer.borderWidth = 1
sessionIDContainer.layer.borderColor = Colors.text.cgColor
// OK button
let okButton = Button(style: .prominentOutline, size: .large)
okButton.set(.width, to: 240)
okButton.setTitle("OK", for: UIControl.State.normal)
okButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside)
// Button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ copyButton, okButton ])
buttonStackView.axis = .vertical
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.alignment = .center
// Main stack view
let stackView = UIStackView(arrangedSubviews: [ topStackView, explanationLabel, sessionIDContainer, buttonStackView ])
stackView.axis = .vertical
stackView.spacing = Values.veryLargeSpacing
stackView.alignment = .center
// Constraints
contentView.addSubview(stackView)
stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.veryLargeSpacing)
stackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.veryLargeSpacing)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.veryLargeSpacing + overshoot)
}
@objc private func copySessionID() {
UIPasteboard.general.string = getUserHexEncodedPublicKey()
copyButton.isUserInteractionEnabled = false
UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.copyButton.setTitle("Copied", for: UIControl.State.normal)
}, completion: nil)
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(enableCopyButton), userInfo: nil, repeats: false)
}
@objc private func enableCopyButton() {
copyButton.isUserInteractionEnabled = true
UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.copyButton.setTitle(NSLocalizedString("copy", comment: ""), for: UIControl.State.normal)
}, completion: nil)
}
}

View File

@ -9,7 +9,6 @@ public final class MentionUtilities : NSObject {
}
@objc public static func highlightMentions(in string: String, isOutgoingMessage: Bool, threadID: String, attributes: [NSAttributedString.Key:Any]) -> NSAttributedString {
let openGroup = Storage.shared.getOpenGroup(for: threadID)
let openGroupV2 = Storage.shared.getV2OpenGroup(for: threadID)
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadID, in: transaction)
@ -23,7 +22,7 @@ public final class MentionUtilities : NSObject {
let publicKey = String((string as NSString).substring(with: match.range).dropFirst()) // Drop the @
let matchEnd: Int
if knownPublicKeys.contains(publicKey) {
let context: Contact.Context = (openGroupV2 != nil || openGroup != nil) ? .openGroup : .regular
let context: Contact.Context = (openGroupV2 != nil) ? .openGroup : .regular
let displayName = Storage.shared.getContact(with: publicKey)?.displayName(for: context)
if let displayName = displayName {
string = (string as NSString).replacingCharacters(in: match.range, with: "@\(displayName)")

View File

@ -7,7 +7,7 @@ extension Storage {
let transaction = transaction as! YapDatabaseReadWriteTransaction
var threadOrNil: TSThread?
if let openGroupID = openGroupID {
if let threadID = Storage.shared.v2GetThreadID(for: openGroupID) ?? Storage.shared.getThreadID(for: openGroupID),
if let threadID = Storage.shared.v2GetThreadID(for: openGroupID),
let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) {
threadOrNil = thread
}

View File

@ -49,26 +49,6 @@ extension Storage {
// MARK: - Quotes
@objc(getServerIDForQuoteWithID:quoteeHexEncodedPublicKey:threadID:transaction:)
public func getServerID(quoteID: UInt64, quoteeHexEncodedPublicKey: String, threadID: String, transaction: YapDatabaseReadTransaction) -> UInt64 {
guard let message = TSInteraction.interactions(withTimestamp: quoteID, filter: { interaction in
let senderPublicKey: String
if let message = interaction as? TSIncomingMessage {
senderPublicKey = message.authorId
} else if interaction is TSOutgoingMessage {
senderPublicKey = getUserHexEncodedPublicKey()
} else {
return false
}
return (senderPublicKey == quoteeHexEncodedPublicKey) && (interaction.uniqueThreadId == threadID)
}, with: transaction).first as! TSMessage? else { return 0 }
return message.openGroupServerMessageID
}
// MARK: - Authorization
private static let authTokenCollection = "SNAuthTokenCollection"
@ -192,10 +172,6 @@ extension Storage {
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: openGroupID, inCollection: Storage.openGroupUserCountCollection)
}
public func setLastProfilePictureUploadDate(_ date: Date) {
UserDefaults.standard[.lastProfilePictureUpload] = date
}
public func getOpenGroupImage(for room: String, on server: String) -> Data? {
var result: Data?
Storage.read { transaction in
@ -207,148 +183,4 @@ extension Storage {
public func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(data, forKey: "\(server).\(room)", inCollection: Storage.openGroupImageCollection)
}
// MARK: - Deprecated
private static let oldOpenGroupUserCountCollection = "LokiPublicChatUserCountCollection"
public func getUserCount(forOpenGroupWithID openGroupID: String) -> Int? {
var result: Int?
Storage.read { transaction in
result = transaction.object(forKey: openGroupID, inCollection: Storage.oldOpenGroupUserCountCollection) as? Int
}
return result
}
public func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any) {
let transaction = transaction as! YapDatabaseReadWriteTransaction
transaction.setObject(newValue, forKey: openGroupID, inCollection: Storage.oldOpenGroupUserCountCollection)
transaction.addCompletionQueue(.main) {
NotificationCenter.default.post(name: .groupThreadUpdated, object: nil)
}
}
private static let oldOpenGroupCollection = "LokiPublicChatCollection"
@objc public func getAllUserOpenGroups() -> [String:OpenGroup] {
var result = [String:OpenGroup]()
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: Storage.oldOpenGroupCollection) { threadID, object, _ in
guard let openGroup = object as? OpenGroup else { return }
result[threadID] = openGroup
}
}
return result
}
@objc(getOpenGroupForThreadID:)
public func getOpenGroup(for threadID: String) -> OpenGroup? {
var result: OpenGroup?
Storage.read { transaction in
result = transaction.object(forKey: threadID, inCollection: Storage.oldOpenGroupCollection) as? OpenGroup
}
return result
}
public func getThreadID(for openGroupID: String) -> String? {
var result: String?
Storage.read { transaction in
transaction.enumerateKeysAndObjects(inCollection: Storage.oldOpenGroupCollection, using: { threadID, object, stop in
guard let openGroup = object as? OpenGroup, "\(openGroup.server).\(openGroup.channel)" == openGroupID else { return }
result = threadID
stop.pointee = true
})
}
return result
}
@objc(setOpenGroup:forThreadWithID:using:)
public func setOpenGroup(_ openGroup: OpenGroup, for threadID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(openGroup, forKey: threadID, inCollection: Storage.oldOpenGroupCollection)
}
@objc(removeOpenGroupForThreadID:using:)
public func removeOpenGroup(for threadID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: threadID, inCollection: Storage.oldOpenGroupCollection)
}
private static func getAuthTokenCollection(for server: String) -> String {
return (server == FileServerAPI.server) ? "LokiStorageAuthTokenCollection" : "LokiGroupChatAuthTokenCollection"
}
public func getAuthToken(for server: String) -> String? {
let collection = Storage.getAuthTokenCollection(for: server)
var result: String? = nil
Storage.read { transaction in
result = transaction.object(forKey: server, inCollection: collection) as? String
}
return result
}
public func setAuthToken(for server: String, to newValue: String, using transaction: Any) {
let collection = Storage.getAuthTokenCollection(for: server)
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: server, inCollection: collection)
}
public func removeAuthToken(for server: String, using transaction: Any) {
let collection = Storage.getAuthTokenCollection(for: server)
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: server, inCollection: collection)
}
public static let oldLastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection"
public func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64? {
var result: UInt64? = nil
Storage.read { transaction in
result = transaction.object(forKey: "\(server).\(group)", inCollection: Storage.oldLastMessageServerIDCollection) as? UInt64
}
return result
}
public func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: "\(server).\(group)", inCollection: Storage.oldLastMessageServerIDCollection)
}
public func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.oldLastMessageServerIDCollection)
}
public static let oldLastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection"
public func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64? {
var result: UInt64? = nil
Storage.read { transaction in
result = transaction.object(forKey: "\(server).\(group)", inCollection: Storage.oldLastDeletionServerIDCollection) as? UInt64
}
return result
}
public func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: "\(server).\(group)", inCollection: Storage.oldLastDeletionServerIDCollection)
}
public func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.oldLastDeletionServerIDCollection)
}
public func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any) {
let collection = openGroupID
(transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection)
}
private static let openGroupProfilePictureURLCollection = "LokiPublicChatAvatarURLCollection"
public func getProfilePictureURL(forOpenGroupWithID openGroupID: String) -> String? {
var result: String?
Storage.read { transaction in
result = transaction.object(forKey: openGroupID, inCollection: Storage.openGroupProfilePictureURLCollection) as? String
}
return result
}
public func setProfilePictureURL(to profilePictureURL: String?, forOpenGroupWithID openGroupID: String, using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(profilePictureURL, forKey: openGroupID, inCollection: Storage.openGroupProfilePictureURLCollection)
}
}

View File

@ -1,76 +0,0 @@
import AFNetworking
import PromiseKit
import SessionSnodeKit
import SessionUtilitiesKit
@objc(SNFileServerAPI)
public final class FileServerAPI : DotNetAPI {
// MARK: Settings
private static let attachmentType = "net.app.core.oembed"
private static let deviceLinkType = "network.loki.messenger.devicemapping"
internal static let publicKey = "62509D59BDEEC404DD0D489C1E15BA8F94FD3D619B01C1BF48A9922BFCB7311C"
public static let maxFileSize = 10_000_000 // 10 MB
/// The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes
/// is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP
/// request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also
/// be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when
/// uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only
/// possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds.
public static let fileSizeORMultiplier: Double = 2
@objc public static let server = "https://file.getsession.org"
@objc public static let fileStorageBucketURL = "https://file-static.lokinet.org"
// MARK: Profile Pictures
@objc(uploadProfilePicture:)
public static func objc_uploadProfilePicture(_ profilePicture: Data) -> AnyPromise {
return AnyPromise.from(uploadProfilePicture(profilePicture))
}
public static func uploadProfilePicture(_ profilePicture: Data) -> Promise<String> {
guard Double(profilePicture.count) < Double(maxFileSize) / fileSizeORMultiplier else { return Promise(error: Error.maxFileSizeExceeded) }
let url = "\(server)/files"
let parameters: JSON = [ "type" : attachmentType, "Content-Type" : "application/binary" ]
var error: NSError?
let request = AFHTTPRequestSerializer().multipartFormRequest(withMethod: "POST", urlString: url, parameters: parameters, constructingBodyWith: { formData in
formData.appendPart(withFileData: profilePicture, name: "content", fileName: UUID().uuidString, mimeType: "application/binary")
}, error: &error)
// Uploads to the Loki File Server shouldn't include any personally identifiable information so use a dummy auth token
request.addValue("Bearer loki", forHTTPHeaderField: "Authorization")
if let error = error {
SNLog("Couldn't upload profile picture due to error: \(error).")
return Promise(error: error)
}
return OnionRequestAPI.sendOnionRequest(request, to: server, using: publicKey).map(on: DispatchQueue.global(qos: .userInitiated)) { json in
guard let data = json["data"] as? JSON, let downloadURL = data["url"] as? String else {
SNLog("Couldn't parse profile picture from: \(json).")
throw Error.parsingFailed
}
SNMessagingKitConfiguration.shared.storage.setLastProfilePictureUploadDate(Date())
return downloadURL
}
}
// MARK: Open Group Server Public Key
public static func getPublicKey(for openGroupServer: String) -> Promise<String> {
guard let host = URL(string: openGroupServer)?.host,
let url = URL(string: "\(server)/loki/v1/getOpenGroupKey/\(host)") else { return Promise(error: DotNetAPI.Error.invalidURL) }
let request = TSRequest(url: url)
let token = "loki" // Tokenless request; use a dummy token
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: publicKey).map(on: DispatchQueue.global(qos: .userInitiated)) { json in
guard let bodyAsString = json["data"] as? String, let bodyAsData = bodyAsString.data(using: .utf8),
let body = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { throw HTTP.Error.invalidJSON }
guard let base64EncodedPublicKey = body["data"] as? String else {
SNLog("Couldn't parse open group public key from: \(body).")
throw Error.parsingFailed
}
let prefixedPublicKey = Data(base64Encoded: base64EncodedPublicKey)!
let hexEncodedPrefixedPublicKey = prefixedPublicKey.toHexString()
return hexEncodedPrefixedPublicKey.removing05PrefixIfNeeded()
}
}
}

View File

@ -9,6 +9,14 @@ public final class FileServerAPIV2 : NSObject {
public static let oldServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69"
@objc public static let server = "http://filev2.getsession.org"
public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59"
public static let maxFileSize = 10_000_000 // 10 MB
/// The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes
/// is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP
/// request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also
/// be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when
/// uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only
/// possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds.
public static let fileSizeORMultiplier: Double = 2
// MARK: Initialization
private override init() { }
@ -17,11 +25,13 @@ public final class FileServerAPIV2 : NSObject {
public enum Error : LocalizedError {
case parsingFailed
case invalidURL
case maxFileSizeExceeded
public var errorDescription: String? {
switch self {
case .parsingFailed: return "Invalid response."
case .invalidURL: return "Invalid URL."
case .maxFileSizeExceeded: return "Maximum file size exceeded."
}
}
}

View File

@ -80,13 +80,6 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
storage.setAttachmentState(to: .failed, for: pointer, associatedWith: self.tsMessageID, using: transaction)
}, completion: { })
self.handlePermanentFailure(error: error)
} else if let error = error as? DotNetAPI.Error, case .parsingFailed = error {
// No need to retry if the response is invalid. Most likely this means we (incorrectly)
// got a "Cannot GET ..." error from the file server.
storage.write(with: { transaction in
storage.setAttachmentState(to: .failed, for: pointer, associatedWith: self.tsMessageID, using: transaction)
}, completion: { })
self.handlePermanentFailure(error: error)
} else {
self.handleFailure(error: error)
}
@ -100,7 +93,7 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
}.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
} else if pointer.downloadURL.contains(FileServerAPIV2.server) || pointer.downloadURL.contains(FileServerAPIV2.oldServer) {
} else {
guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let file = UInt64(fileAsString) else {
return handleFailure(Error.invalidURL)
}
@ -110,12 +103,6 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject
}.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
} else { // Legacy
FileServerAPI.downloadAttachment(from: pointer.downloadURL).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
self.handleDownloadedAttachment(data: data, temporaryFilePath: temporaryFilePath, pointer: pointer, failureHandler: handleFailure)
}.catch(on: DispatchQueue.global()) { error in
handleFailure(error)
}
}
}

View File

@ -67,23 +67,8 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
let storage = SNMessagingKitConfiguration.shared.storage
if let v2OpenGroup = storage.getV2OpenGroup(for: threadID) {
AttachmentUploadJob.upload(stream, using: { data in return OpenGroupAPIV2.upload(data, to: v2OpenGroup.room, on: v2OpenGroup.server) }, encrypt: false, onSuccess: handleSuccess, onFailure: handleFailure)
} else if Features.useV2FileServer && storage.getOpenGroup(for: threadID) == nil {
} else {
AttachmentUploadJob.upload(stream, using: FileServerAPIV2.upload, encrypt: true, onSuccess: handleSuccess, onFailure: handleFailure)
} else { // Legacy
let openGroup = storage.getOpenGroup(for: threadID)
let server = openGroup?.server ?? FileServerAPI.server
// FIXME: A lot of what's currently happening in FileServerAPI should really be happening here
FileServerAPI.uploadAttachment(stream, with: attachmentID, to: server).done(on: DispatchQueue.global(qos: .userInitiated)) { // Intentionally capture self
self.handleSuccess()
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
if let error = error as? Error, case .noAttachment = error {
self.handlePermanentFailure(error: error)
} else if let error = error as? DotNetAPI.Error, !error.isRetryable {
self.handlePermanentFailure(error: error)
} else {
self.handleFailure(error: error)
}
}
}
}
@ -107,8 +92,8 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/N
}
// Check the file size
SNLog("File size: \(data.count) bytes.")
if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier {
onFailure?(FileServerAPI.Error.maxFileSizeExceeded); return
if Double(data.count) > Double(FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier {
onFailure?(FileServerAPIV2.Error.maxFileSizeExceeded); return
}
// Send the request
stream.isUploaded = false

View File

@ -15,12 +15,8 @@ public extension Message {
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
return .closedGroup(groupPublicKey: groupPublicKey)
} else if let thread = thread as? TSGroupThread, thread.isOpenGroup {
if let openGroupV2 = Storage.shared.getV2OpenGroup(for: thread.uniqueId!) {
return .openGroupV2(room: openGroupV2.room, server: openGroupV2.server)
} else {
let openGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!)!
return .openGroup(channel: openGroup.channel, server: openGroup.server)
}
let openGroupV2 = Storage.shared.getV2OpenGroup(for: thread.uniqueId!)!
return .openGroupV2(room: openGroupV2.room, server: openGroupV2.server)
} else {
preconditionFailure("TODO: Handle legacy closed groups.")
}

View File

@ -19,9 +19,6 @@ typedef NS_ENUM(NSInteger, TSOutgoingMessageState) {
TSOutgoingMessageStateSending,
// The failure state.
TSOutgoingMessageStateFailed,
// These two enum values have been combined into TSOutgoingMessageStateSent.
TSOutgoingMessageStateSent_OBSOLETE,
TSOutgoingMessageStateDelivered_OBSOLETE,
// The message has been sent to the service.
TSOutgoingMessageStateSent,
};
@ -135,42 +132,8 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
@property (nonatomic, readonly) BOOL isVoiceMessage;
// This property won't be accurate for legacy messages.
@property (atomic, readonly) BOOL isFromLinkedDevice;
@property (nonatomic, readonly) BOOL isSilent;
@property (nonatomic, readonly) BOOL isOnline;
@property (nonatomic, readonly) uint ttl;
+ (nullable instancetype)findMessageWithTimestamp:(uint64_t)timestamp;
/**
* The data representation of this message, to be encrypted, before being sent.
*/
- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient;
/**
* Intermediate protobuf representation
* Subclasses can augment if they want to manipulate the data message before building.
*/
- (nullable id)dataMessageBuilder;
- (nullable SNProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId;
/**
* Allows subclasses to supply a custom content builder that has already prepared part of the message.
*/
- (nullable id)prepareCustomContentBuilder:(SignalRecipient *)recipient;
/**
* Should this message be synced to the users other registered devices? This is
* generally always true, except in the case of the sync messages themseleves
* (so we don't end up in an infinite loop).
*/
- (BOOL)shouldSyncTranscript;
- (BOOL)shouldBeSaved;
// All recipients of this message.

View File

@ -37,10 +37,6 @@ NSString *NSStringForOutgoingMessageState(TSOutgoingMessageState value)
return @"TSOutgoingMessageStateSending";
case TSOutgoingMessageStateFailed:
return @"TSOutgoingMessageStateFailed";
case TSOutgoingMessageStateSent_OBSOLETE:
return @"TSOutgoingMessageStateSent_OBSOLETE";
case TSOutgoingMessageStateDelivered_OBSOLETE:
return @"TSOutgoingMessageStateDelivered_OBSOLETE";
case TSOutgoingMessageStateSent:
return @"TSOutgoingMessageStateSent";
}
@ -82,13 +78,7 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
@property (atomic) BOOL hasSyncedTranscript;
@property (atomic) NSString *customMessage;
@property (atomic) NSString *mostRecentFailureText;
@property (atomic) BOOL isFromLinkedDevice;
@property (atomic) TSGroupMetaMessage groupMetaMessage;
@property (nonatomic, readonly) TSOutgoingMessageState legacyMessageState;
@property (nonatomic, readonly) BOOL legacyWasDelivered;
@property (nonatomic, readonly) BOOL hasLegacyMessageState;
@property (atomic, nullable) NSDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap;
@end
@ -105,118 +95,11 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
if (!_attachmentFilenameMap) {
_attachmentFilenameMap = [NSMutableDictionary new];
}
if (!self.recipientStateMap) {
[self migrateRecipientStateMapWithCoder:coder];
}
}
return self;
}
- (void)migrateRecipientStateMapWithCoder:(NSCoder *)coder
{
// Determine the "overall message state."
TSOutgoingMessageState oldMessageState = TSOutgoingMessageStateFailed;
NSNumber *_Nullable messageStateValue = [coder decodeObjectForKey:@"messageState"];
if (messageStateValue) {
oldMessageState = (TSOutgoingMessageState)messageStateValue.intValue;
}
_hasLegacyMessageState = YES;
_legacyMessageState = oldMessageState;
OWSOutgoingMessageRecipientState defaultState;
switch (oldMessageState) {
case TSOutgoingMessageStateFailed:
defaultState = OWSOutgoingMessageRecipientStateFailed;
break;
case TSOutgoingMessageStateSending:
defaultState = OWSOutgoingMessageRecipientStateSending;
break;
case TSOutgoingMessageStateSent:
case TSOutgoingMessageStateSent_OBSOLETE:
case TSOutgoingMessageStateDelivered_OBSOLETE:
// Convert legacy values.
defaultState = OWSOutgoingMessageRecipientStateSent;
break;
}
// Try to leverage the "per-recipient state."
NSDictionary<NSString *, NSNumber *> *_Nullable recipientDeliveryMap =
[coder decodeObjectForKey:@"recipientDeliveryMap"];
NSDictionary<NSString *, NSNumber *> *_Nullable recipientReadMap = [coder decodeObjectForKey:@"recipientReadMap"];
NSArray<NSString *> *_Nullable sentRecipients = [coder decodeObjectForKey:@"sentRecipients"];
NSMutableDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap = [NSMutableDictionary new];
__block BOOL isGroupThread = NO;
// Our default recipient list is the current thread members.
__block NSArray<NSString *> *recipientIds = @[];
// To avoid deadlock while migrating these records, we use a dedicated
// migration connection. For legacy records (created more than ~9 months
// before the migration), we need to infer the recipient list for this
// message from the current thread membership. This inference isn't
// always accurate, so not using the same connection for both reads is
// acceptable.
[TSOutgoingMessage.dbMigrationConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
TSThread *thread = [self threadWithTransaction:transaction];
recipientIds = [thread recipientIdentifiers];
isGroupThread = [thread isGroupThread];
}];
NSNumber *_Nullable wasDelivered = [coder decodeObjectForKey:@"wasDelivered"];
_legacyWasDelivered = wasDelivered && wasDelivered.boolValue;
BOOL wasDeliveredToContact = NO;
if (isGroupThread) {
// If we have a `sentRecipients` list, prefer that as it is more accurate.
if (sentRecipients) {
recipientIds = sentRecipients;
}
} else {
// Special-case messages in contact threads; if "was delivered", we know
// it was delivered to the contact.
wasDeliveredToContact = _legacyWasDelivered;
}
NSString *_Nullable singleGroupRecipient = [coder decodeObjectForKey:@"singleGroupRecipient"];
if (singleGroupRecipient) {
// If this is a "single group recipient message", treat it as such.
recipientIds = @[
singleGroupRecipient,
];
}
for (NSString *recipientId in recipientIds) {
TSOutgoingMessageRecipientState *recipientState = [TSOutgoingMessageRecipientState new];
NSNumber *_Nullable readTimestamp = recipientReadMap[recipientId];
NSNumber *_Nullable deliveryTimestamp = recipientDeliveryMap[recipientId];
if (readTimestamp) {
// If we have a read timestamp for this recipient, mark it as read.
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.readTimestamp = readTimestamp;
// deliveryTimestamp might be nil here.
recipientState.deliveryTimestamp = deliveryTimestamp;
} else if (deliveryTimestamp) {
// If we have a delivery timestamp for this recipient, mark it as delivered.
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.deliveryTimestamp = deliveryTimestamp;
} else if (wasDeliveredToContact) {
recipientState.state = OWSOutgoingMessageRecipientStateSent;
// Use message time as an estimate of delivery time.
recipientState.deliveryTimestamp = @(self.timestamp);
} else if ([sentRecipients containsObject:recipientId]) {
// If this recipient is in `sentRecipients`, mark it as sent.
recipientState.state = OWSOutgoingMessageRecipientStateSent;
} else {
// Use the default state for this message.
recipientState.state = defaultState;
}
recipientStateMap[recipientId] = recipientState;
}
self.recipientStateMap = [recipientStateMap copy];
}
+ (YapDatabaseConnection *)dbMigrationConnection
{
return SSKEnvironment.shared.migrationDBConnection;
@ -388,30 +271,17 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
- (TSOutgoingMessageState)messageState
{
TSOutgoingMessageState newMessageState =
[TSOutgoingMessage messageStateForRecipientStates:self.recipientStateMap.allValues];
if (self.hasLegacyMessageState) {
if (newMessageState == TSOutgoingMessageStateSent || self.legacyMessageState == TSOutgoingMessageStateSent) {
return TSOutgoingMessageStateSent;
}
}
return newMessageState;
return [TSOutgoingMessage messageStateForRecipientStates:self.recipientStateMap.allValues];
}
- (BOOL)wasDeliveredToAnyRecipient
{
if ([self deliveredRecipientIds].count > 0) {
return YES;
}
return (self.hasLegacyMessageState && self.legacyWasDelivered && self.messageState == TSOutgoingMessageStateSent);
return [self deliveredRecipientIds].count > 0;
}
- (BOOL)wasSentToAnyRecipient
{
if ([self sentRecipientIds].count > 0) {
return YES;
}
return (self.hasLegacyMessageState && self.messageState == TSOutgoingMessageStateSent);
return [self sentRecipientIds].count > 0;
}
+ (TSOutgoingMessageState)messageStateForRecipientStates:(NSArray<TSOutgoingMessageRecipientState *> *)recipientStates
@ -485,16 +355,6 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
}
}
- (BOOL)isSilent
{
return NO;
}
- (BOOL)isOnline
{
return NO;
}
+ (nullable instancetype)findMessageWithTimestamp:(uint64_t)timestamp
{
__block TSOutgoingMessage *result;
@ -628,15 +488,6 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
}];
}
- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setHasSyncedTranscript:hasSyncedTranscript];
}];
}
- (void)updateWithCustomMessage:(NSString *)customMessage transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self applyChangeToSelfAndLatestCopy:transaction
@ -708,325 +559,6 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
}];
}
- (void)updateWithWasSentFromLinkedDeviceWithUDRecipientIds:(nullable NSArray<NSString *> *)udRecipientIds
nonUdRecipientIds:(nullable NSArray<NSString *> *)nonUdRecipientIds
isSentUpdate:(BOOL)isSentUpdate
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self
applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
if (udRecipientIds.count > 0 || nonUdRecipientIds.count > 0) {
// If we have specific recipient info from the transcript,
// build a new recipient state map.
NSMutableDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap
= [NSMutableDictionary new];
for (NSString *recipientId in udRecipientIds) {
if (recipientStateMap[recipientId]) {
continue;
}
TSOutgoingMessageRecipientState *recipientState =
[TSOutgoingMessageRecipientState new];
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.wasSentByUD = YES;
recipientStateMap[recipientId] = recipientState;
}
for (NSString *recipientId in nonUdRecipientIds) {
if (recipientStateMap[recipientId]) {
continue;
}
TSOutgoingMessageRecipientState *recipientState =
[TSOutgoingMessageRecipientState new];
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.wasSentByUD = NO;
recipientStateMap[recipientId] = recipientState;
}
if (isSentUpdate) {
// If this is a "sent update", make sure that:
//
// a) "Sent updates" should never remove any recipients. We end up with the
// union of the existing and new recipients.
// b) "Sent updates" should never downgrade the "recipient state" for any
// recipients. Prefer existing "recipient state"; "sent updates" only
// add new recipients at the "sent" state.
//
// Therefore we retain all existing entries in the recipient state map.
[recipientStateMap addEntriesFromDictionary:self.recipientStateMap];
}
[message setRecipientStateMap:recipientStateMap];
} else {
// Otherwise assume this is a legacy message before UD was introduced, and mark
// any "sending" recipient as "sent". Note that this will apply to non-legacy
// messages with no recipients.
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
.allValues) {
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
recipientState.state = OWSOutgoingMessageRecipientStateSent;
}
}
}
if (!isSentUpdate) {
[message setIsFromLinkedDevice:YES];
}
}];
}
- (void)updateWithSendingToSingleGroupRecipient:(NSString *)singleGroupRecipient
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
TSOutgoingMessageRecipientState *recipientState =
[TSOutgoingMessageRecipientState new];
recipientState.state = OWSOutgoingMessageRecipientStateSending;
[message setRecipientStateMap:@{
singleGroupRecipient : recipientState,
}];
}];
}
- (nullable NSNumber *)firstRecipientReadTimestamp
{
NSNumber *result = nil;
for (TSOutgoingMessageRecipientState *recipientState in self.recipientStateMap.allValues) {
if (!recipientState.readTimestamp) {
continue;
}
if (!result || (result.unsignedLongLongValue > recipientState.readTimestamp.unsignedLongLongValue)) {
result = recipientState.readTimestamp;
}
}
return result;
}
- (void)updateWithFakeMessageState:(TSOutgoingMessageState)messageState
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
.allValues) {
switch (messageState) {
case TSOutgoingMessageStateSending:
recipientState.state = OWSOutgoingMessageRecipientStateSending;
break;
case TSOutgoingMessageStateFailed:
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
break;
case TSOutgoingMessageStateSent:
recipientState.state = OWSOutgoingMessageRecipientStateSent;
break;
default:
break;
}
}
}];
}
#pragma mark -
- (nullable id)dataMessageBuilder
{
TSThread *thread = self.thread;
SNProtoDataMessageBuilder *builder = [SNProtoDataMessage builder];
[builder setTimestamp:self.timestamp];
if ([self.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold) {
[builder setBody:self.body];
} else {
NSString *truncatedBody = [self.body copy];
while ([truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kOversizeTextMessageSizeThreshold) {
truncatedBody = [truncatedBody substringToIndex:truncatedBody.length / 2];
}
[builder setBody:truncatedBody];
}
[builder setExpireTimer:self.expiresInSeconds];
// Group Messages
BOOL attachmentWasGroupAvatar = NO;
if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *gThread = (TSGroupThread *)thread;
SNProtoGroupContextType groupMessageType;
switch (self.groupMetaMessage) {
case TSGroupMetaMessageQuit:
groupMessageType = SNProtoGroupContextTypeQuit;
break;
case TSGroupMetaMessageUpdate:
case TSGroupMetaMessageNew:
groupMessageType = SNProtoGroupContextTypeUpdate;
break;
default:
groupMessageType = SNProtoGroupContextTypeDeliver;
break;
}
SNProtoGroupContextBuilder *groupBuilder =
[SNProtoGroupContext builderWithId:gThread.groupModel.groupId type:groupMessageType];
if (groupMessageType == SNProtoGroupContextTypeUpdate) {
if (gThread.groupModel.groupImage != nil && self.attachmentIds.count == 1) {
attachmentWasGroupAvatar = YES;
SNProtoAttachmentPointer *_Nullable attachmentProto =
[TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject];
if (!attachmentProto) {
return nil;
}
[groupBuilder setAvatar:attachmentProto];
}
[groupBuilder setMembers:gThread.groupModel.groupMemberIds];
[groupBuilder setName:gThread.groupModel.groupName];
[groupBuilder setAdmins:gThread.groupModel.groupAdminIds];
}
NSError *error;
SNProtoGroupContext *_Nullable groupContextProto = [groupBuilder buildAndReturnError:&error];
if (error || !groupContextProto) {
return nil;
}
[builder setGroup:groupContextProto];
}
// Message Attachments
if (!attachmentWasGroupAvatar) {
NSMutableArray *attachments = [NSMutableArray new];
for (NSString *attachmentId in self.attachmentIds) {
SNProtoAttachmentPointer *_Nullable attachmentProto =
[TSAttachmentStream buildProtoForAttachmentId:attachmentId];
if (!attachmentProto) {
return nil;
}
[attachments addObject:attachmentProto];
}
[builder setAttachments:attachments];
}
// Quoted Reply
SNProtoDataMessageQuoteBuilder *_Nullable quotedMessageBuilder = self.quotedMessageBuilder;
if (quotedMessageBuilder) {
NSError *error;
SNProtoDataMessageQuote *_Nullable quoteProto = [quotedMessageBuilder buildAndReturnError:&error];
if (error || !quoteProto) {
return nil;
}
[builder setQuote:quoteProto];
}
// Link Preview
if (self.linkPreview) {
SNProtoDataMessagePreviewBuilder *previewBuilder =
[SNProtoDataMessagePreview builderWithUrl:self.linkPreview.urlString];
if (self.linkPreview.title.length > 0) {
[previewBuilder setTitle:self.linkPreview.title];
}
if (self.linkPreview.imageAttachmentId) {
SNProtoAttachmentPointer *_Nullable attachmentProto =
[TSAttachmentStream buildProtoForAttachmentId:self.linkPreview.imageAttachmentId];
if (!attachmentProto) {
} else {
[previewBuilder setImage:attachmentProto];
}
}
NSError *error;
SNProtoDataMessagePreview *_Nullable previewProto = [previewBuilder buildAndReturnError:&error];
if (error || !previewProto) {
} else {
[builder addPreview:previewProto];
}
}
return builder;
}
- (nullable SNProtoDataMessageQuoteBuilder *)quotedMessageBuilder
{
if (!self.quotedMessage) {
return nil;
}
TSQuotedMessage *quotedMessage = self.quotedMessage;
SNProtoDataMessageQuoteBuilder *quoteBuilder =
[SNProtoDataMessageQuote builderWithId:quotedMessage.timestamp author:quotedMessage.authorId];
BOOL hasQuotedText = NO;
BOOL hasQuotedAttachment = NO;
if (self.quotedMessage.body.length > 0) {
hasQuotedText = YES;
[quoteBuilder setText:quotedMessage.body];
}
if (quotedMessage.quotedAttachments) {
for (OWSAttachmentInfo *attachment in quotedMessage.quotedAttachments) {
hasQuotedAttachment = YES;
SNProtoDataMessageQuoteQuotedAttachmentBuilder *quotedAttachmentBuilder =
[SNProtoDataMessageQuoteQuotedAttachment builder];
quotedAttachmentBuilder.contentType = attachment.contentType;
quotedAttachmentBuilder.fileName = attachment.sourceFilename;
if (attachment.thumbnailAttachmentStreamId) {
quotedAttachmentBuilder.thumbnail =
[TSAttachmentStream buildProtoForAttachmentId:attachment.thumbnailAttachmentStreamId];
}
NSError *error;
SNProtoDataMessageQuoteQuotedAttachment *_Nullable quotedAttachmentMessage =
[quotedAttachmentBuilder buildAndReturnError:&error];
if (error || !quotedAttachmentMessage) {
return nil;
}
[quoteBuilder addAttachments:quotedAttachmentMessage];
}
}
if (hasQuotedText || hasQuotedAttachment) {
return quoteBuilder;
} else {
return nil;
}
}
// recipientId is nil when building "sent" sync messages for messages sent to groups.
- (nullable SNProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId
{
return nil; // Shouldn't be in use anymore
}
- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
SNProtoContentBuilder *contentBuilder = [self prepareCustomContentBuilder:recipient];
NSError *error;
NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error];
if (error != nil || contentData == nil) {
return nil;
}
return contentData;
}
- (BOOL)shouldSyncTranscript
{
return YES;
}
- (NSString *)statusDescription
{
NSMutableString *result = [NSMutableString new];
[result appendFormat:@"[status: %@", NSStringForOutgoingMessageState(self.messageState)];
for (NSString *recipientId in self.recipientStateMap) {
TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId];
[result appendFormat:@", %@: %@", recipientId, NSStringForOutgoingMessageRecipientState(recipientState.state)];
}
[result appendString:@"]"];
return [result copy];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,43 +0,0 @@
@objc(SNOpenGroup)
public final class OpenGroup : NSObject, NSCoding {
@objc public let id: String
@objc public let idAsData: Data
@objc public let channel: UInt64
@objc public let server: String
@objc public let displayName: String
@objc public let isDeletable: Bool
@objc public init?(channel: UInt64, server: String, displayName: String, isDeletable: Bool) {
let id = "\(server).\(channel)"
self.id = id
guard let idAsData = id.data(using: .utf8) else { return nil }
self.idAsData = idAsData
self.channel = channel
self.server = server.lowercased()
self.displayName = displayName
self.isDeletable = isDeletable
}
// MARK: Coding
@objc public init?(coder: NSCoder) {
channel = UInt64(coder.decodeInt64(forKey: "channel"))
server = coder.decodeObject(forKey: "server") as! String
let id = "\(server).\(channel)"
self.id = id
guard let idAsData = id.data(using: .utf8) else { return nil }
self.idAsData = idAsData
displayName = coder.decodeObject(forKey: "displayName") as! String
isDeletable = coder.decodeBool(forKey: "isDeletable")
super.init()
}
@objc public func encode(with coder: NSCoder) {
coder.encode(Int64(channel), forKey: "channel")
coder.encode(server, forKey: "server")
coder.encode(displayName, forKey: "displayName")
coder.encode(isDeletable, forKey: "isDeletable")
}
override public var description: String { "\(displayName) (\(server))" }
}

View File

@ -1,514 +0,0 @@
import AFNetworking
import PromiseKit
import SessionSnodeKit
import SessionUtilitiesKit
@objc(SNOpenGroupAPI)
public final class OpenGroupAPI : DotNetAPI {
private static var moderators: [String:[UInt64:Set<String>]] = [:] // Server URL to (channel ID to set of moderator IDs)
public static var displayNameUpdatees: [String:Set<String>] = [:]
// MARK: Settings
private static let attachmentType = "net.app.core.oembed"
private static let channelInfoType = "net.patter-app.settings"
private static let fallbackBatchCount = 64
private static let maxRetryCount: UInt = 4
public static let profilePictureType = "network.loki.messenger.avatar"
@objc public static let openGroupMessageType = "network.loki.messenger.publicChat"
// MARK: Open Group Public Key Validation
public static func getOpenGroupServerPublicKey(for server: String) -> Promise<String> {
if let publicKey = SNMessagingKitConfiguration.shared.storage.getOpenGroupPublicKey(for: server) {
return Promise.value(publicKey)
} else {
return FileServerAPI.getPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { publicKey -> Promise<String> in
let url = URL(string: server)!
let request = TSRequest(url: url)
return OnionRequestAPI.sendOnionRequest(request, to: server, using: publicKey, isJSONRequired: false).map(on: DispatchQueue.global(qos: .default)) { _ -> String in
SNMessagingKitConfiguration.shared.storage.writeSync { transaction in
SNMessagingKitConfiguration.shared.storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
}
return publicKey
}
}
}
}
// MARK: Receiving
@objc(getMessagesForGroup:onServer:)
public static func objc_getMessages(for group: UInt64, on server: String) -> AnyPromise {
return AnyPromise.from(getMessages(for: group, on: server))
}
public static func getMessages(for channel: UInt64, on server: String) -> Promise<[OpenGroupMessage]> {
let storage = SNMessagingKitConfiguration.shared.storage
var queryParameters = "include_annotations=1"
if let lastMessageServerID = storage.getLastMessageServerID(for: channel, on: server) {
queryParameters += "&since_id=\(lastMessageServerID)"
} else {
queryParameters += "&count=\(fallbackBatchCount)&include_deleted=0"
}
return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<[OpenGroupMessage]> in
let url = URL(string: "\(server)/channels/\(channel)/messages?\(queryParameters)")!
let request = TSRequest(url: url)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in
guard let rawMessages = json["data"] as? [JSON] else {
SNLog("Couldn't parse messages for open group channel with ID: \(channel) on server: \(server) from: \(json).")
throw Error.parsingFailed
}
return rawMessages.compactMap { message in
let isDeleted = (message["is_deleted"] as? Int == 1)
guard !isDeleted else { return nil }
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
guard let annotations = message["annotations"] as? [JSON], let annotation = annotations.first(where: { $0["type"] as? String == openGroupMessageType }), let value = annotation["value"] as? JSON,
let serverID = message["id"] as? UInt64, let hexEncodedSignatureData = value["sig"] as? String, let signatureVersion = value["sigver"] as? UInt64,
let body = message["text"] as? String, let user = message["user"] as? JSON, let hexEncodedPublicKey = user["username"] as? String,
let timestamp = value["timestamp"] as? UInt64, let dateAsString = message["created_at"] as? String, let date = dateFormatter.date(from: dateAsString) else {
SNLog("Couldn't parse message for open group channel with ID: \(channel) on server: \(server) from: \(message).")
return nil
}
let serverTimestamp = UInt64(date.timeIntervalSince1970) * 1000
var profilePicture: OpenGroupMessage.ProfilePicture? = nil
let displayName = user["name"] as? String ?? NSLocalizedString("Anonymous", comment: "")
if let userAnnotations = user["annotations"] as? [JSON], let profilePictureAnnotation = userAnnotations.first(where: { $0["type"] as? String == profilePictureType }),
let profilePictureValue = profilePictureAnnotation["value"] as? JSON, let profileKeyString = profilePictureValue["profileKey"] as? String, let profileKey = Data(base64Encoded: profileKeyString), let url = profilePictureValue["url"] as? String {
profilePicture = OpenGroupMessage.ProfilePicture(profileKey: profileKey, url: url)
}
let lastMessageServerID = storage.getLastMessageServerID(for: channel, on: server)
if serverID > (lastMessageServerID ?? 0) {
storage.writeSync { transaction in
storage.setLastMessageServerID(for: channel, on: server, to: serverID, using: transaction)
}
}
let quote: OpenGroupMessage.Quote?
if let quoteAsJSON = value["quote"] as? JSON, let quotedMessageTimestamp = quoteAsJSON["id"] as? UInt64, let quoteePublicKey = quoteAsJSON["author"] as? String,
let quotedMessageBody = quoteAsJSON["text"] as? String {
let quotedMessageServerID = message["reply_to"] as? UInt64
quote = OpenGroupMessage.Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteePublicKey: quoteePublicKey, quotedMessageBody: quotedMessageBody,
quotedMessageServerID: quotedMessageServerID)
} else {
quote = nil
}
let signature = OpenGroupMessage.Signature(data: Data(hex: hexEncodedSignatureData), version: signatureVersion)
let attachmentsAsJSON = annotations.filter { $0["type"] as? String == attachmentType }
let attachments: [OpenGroupMessage.Attachment] = attachmentsAsJSON.compactMap { attachmentAsJSON in
guard let value = attachmentAsJSON["value"] as? JSON, let kindAsString = value["lokiType"] as? String, let kind = OpenGroupMessage.Attachment.Kind(rawValue: kindAsString),
let serverID = value["id"] as? UInt64, let contentType = value["contentType"] as? String, let size = value["size"] as? UInt, let url = value["url"] as? String else { return nil }
let fileName = value["fileName"] as? String ?? UUID().description
let width = value["width"] as? UInt ?? 0
let height = value["height"] as? UInt ?? 0
let flags = (value["flags"] as? UInt) ?? 0
let caption = value["caption"] as? String
let linkPreviewURL = value["linkPreviewUrl"] as? String
let linkPreviewTitle = value["linkPreviewTitle"] as? String
if kind == .linkPreview {
guard linkPreviewURL != nil && linkPreviewTitle != nil else {
SNLog("Ignoring open group message with invalid link preview.")
return nil
}
}
return OpenGroupMessage.Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags,
width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle)
}
let result = OpenGroupMessage(serverID: serverID, senderPublicKey: hexEncodedPublicKey, displayName: displayName, profilePicture: profilePicture,
body: body, type: openGroupMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature, serverTimestamp: serverTimestamp)
guard result.hasValidSignature() else {
SNLog("Ignoring open group message with invalid signature.")
return nil
}
return result
}.sorted { $0.serverTimestamp < $1.serverTimestamp}
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
// MARK: Sending
@objc(sendMessage:toGroup:onServer:)
public static func objc_sendMessage(_ message: OpenGroupMessage, to group: UInt64, on server: String) -> AnyPromise {
return AnyPromise.from(sendMessage(message, to: group, on: server))
}
public static func sendMessage(_ message: OpenGroupMessage, to channel: UInt64, on server: String) -> Promise<OpenGroupMessage> {
SNLog("Sending message to open group channel with ID: \(channel) on server: \(server).")
let storage = SNMessagingKitConfiguration.shared.storage
guard let userKeyPair = storage.getUserKeyPair(),
let userName = storage.getUser()?.name else { return Promise(error: Error.generic) }
let (promise, seal) = Promise<OpenGroupMessage>.pending()
DispatchQueue.global(qos: .userInitiated).async { [privateKey = userKeyPair.privateKey] in
guard let signedMessage = message.sign(with: privateKey) else { return seal.reject(Error.signingFailed) }
attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<OpenGroupMessage> in
let url = URL(string: "\(server)/channels/\(channel)/messages")!
let parameters = signedMessage.toJSON()
let request = TSRequest(url: url, method: "POST", parameters: parameters)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
let userName = userName
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in
// ISO8601DateFormatter doesn't support milliseconds before iOS 11
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
guard let messageAsJSON = json["data"] as? JSON, let serverID = messageAsJSON["id"] as? UInt64, let body = messageAsJSON["text"] as? String,
let dateAsString = messageAsJSON["created_at"] as? String, let date = dateFormatter.date(from: dateAsString) else {
SNLog("Couldn't parse message for open group channel with ID: \(channel) on server: \(server) from: \(json).")
throw Error.parsingFailed
}
let timestamp = UInt64(date.timeIntervalSince1970) * 1000
return OpenGroupMessage(serverID: serverID, senderPublicKey: userKeyPair.publicKey.toHexString(), displayName: userName, profilePicture: signedMessage.profilePicture, body: body, type: openGroupMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature, serverTimestamp: timestamp)
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}.done(on: DispatchQueue.global(qos: .default)) { message in
seal.fulfill(message)
}.catch(on: DispatchQueue.global(qos: .default)) { error in
seal.reject(error)
}
}
return promise
}
// MARK: Deletion
public static func getDeletedMessageServerIDs(for channel: UInt64, on server: String) -> Promise<[UInt64]> {
SNLog("Getting deleted messages for open group channel with ID: \(channel) on server: \(server).")
let storage = SNMessagingKitConfiguration.shared.storage
let queryParameters: String
if let lastDeletionServerID = storage.getLastDeletionServerID(for: channel, on: server) {
queryParameters = "since_id=\(lastDeletionServerID)"
} else {
queryParameters = "count=\(fallbackBatchCount)"
}
return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<[UInt64]> in
let url = URL(string: "\(server)/loki/v1/channel/\(channel)/deletes?\(queryParameters)")!
let request = TSRequest(url: url)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in
guard let body = json["body"] as? JSON, let deletions = body["data"] as? [JSON] else {
SNLog("Couldn't parse deleted messages for open group channel with ID: \(channel) on server: \(server) from: \(json).")
throw Error.parsingFailed
}
return deletions.compactMap { deletion in
guard let serverID = deletion["id"] as? UInt64, let messageServerID = deletion["message_id"] as? UInt64 else {
SNLog("Couldn't parse deleted message for open group channel with ID: \(channel) on server: \(server) from: \(deletion).")
return nil
}
let lastDeletionServerID = storage.getLastDeletionServerID(for: channel, on: server)
if serverID > (lastDeletionServerID ?? 0) {
storage.writeSync { transaction in
storage.setLastDeletionServerID(for: channel, on: server, to: serverID, using: transaction)
}
}
return messageServerID
}
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
@objc(deleteMessageWithID:forGroup:onServer:isSentByUser:)
public static func objc_deleteMessage(with messageID: UInt, for group: UInt64, on server: String, isSentByUser: Bool) -> AnyPromise {
return AnyPromise.from(deleteMessage(with: messageID, for: group, on: server, wasSentByUser: isSentByUser))
}
public static func deleteMessage(with messageID: UInt, for channel: UInt64, on server: String, wasSentByUser: Bool) -> Promise<Void> {
let isModerationRequest = !wasSentByUser
SNLog("Deleting message with ID: \(messageID) for open group channel with ID: \(channel) on server: \(server) (isModerationRequest = \(isModerationRequest)).")
let urlAsString = wasSentByUser ? "\(server)/channels/\(channel)/messages/\(messageID)" : "\(server)/loki/v1/moderation/message/\(messageID)"
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let url = URL(string: urlAsString)!
let request = TSRequest(url: url, method: "DELETE", parameters: [:])
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false).done(on: DispatchQueue.global(qos: .default)) { _ -> Void in
SNLog("Deleted message with ID: \(messageID) on server: \(server).")
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Banning
@objc(banPublicKey:fromServer:)
public static func objc_ban(_ publicKey: String, from server: String) -> AnyPromise {
return AnyPromise.from(ban(publicKey, from: server))
}
public static func ban(_ publicKey: String, from server: String) -> Promise<Void> {
SNLog("Banning user with ID: \(publicKey) from server: \(server).")
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let url = URL(string: "\(server)/loki/v1/moderation/blacklist/@\(publicKey)")!
let request = TSRequest(url: url, method: "POST", parameters: [:])
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
let promise = OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false)
let _ = promise.done(on: DispatchQueue.global(qos: .default)) { _ -> Void in
SNLog("Banned user with ID: \(publicKey) from server: \(server).")
}
promise.catch(on: DispatchQueue.main) { error in
print(error)
}
return promise.map { _ in }
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Display Name & Profile Picture
public static func getDisplayNames(for channel: UInt64, on server: String) -> Promise<Void> {
let openGroupID = "\(server).\(channel)"
guard let publicKeys = displayNameUpdatees[openGroupID] else { return Promise.value(()) }
displayNameUpdatees[openGroupID] = []
SNLog("Getting display names for: \(publicKeys).")
return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let queryParameters = "ids=\(publicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1"
let url = URL(string: "\(server)/users?\(queryParameters)")!
let request = TSRequest(url: url)
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in
guard let data = json["data"] as? [JSON] else {
SNLog("Couldn't parse display names for users: \(publicKeys) from: \(json).")
throw Error.parsingFailed
}
let storage = SNMessagingKitConfiguration.shared.storage
storage.writeSync { transaction in
data.forEach { data in
guard let user = data["user"] as? JSON, let hexEncodedPublicKey = user["username"] as? String, let rawDisplayName = user["name"] as? String else { return }
let endIndex = hexEncodedPublicKey.endIndex
let cutoffIndex = hexEncodedPublicKey.index(endIndex, offsetBy: -8)
let displayName = "\(rawDisplayName) (...\(hexEncodedPublicKey[cutoffIndex..<endIndex]))"
storage.setOpenGroupDisplayName(to: displayName, for: hexEncodedPublicKey, inOpenGroupWithID: "\(server).\(channel)", using: transaction)
}
}
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
@objc(setDisplayName:on:)
public static func objc_setDisplayName(to newDisplayName: String?, on server: String) -> AnyPromise {
return AnyPromise.from(setDisplayName(to: newDisplayName, on: server))
}
public static func setDisplayName(to newDisplayName: String?, on server: String) -> Promise<Void> {
SNLog("Updating display name on server: \(server).")
let parameters: JSON = [ "name" : (newDisplayName ?? "") ]
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let url = URL(string: "\(server)/users/me")!
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { _ in }.recover(on: DispatchQueue.global(qos: .default)) { error in
print("Couldn't update display name due to error: \(error).")
throw error
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
@objc(setProfilePictureURL:usingProfileKey:on:)
public static func objc_setProfilePicture(to url: String?, using profileKey: Data, on server: String) -> AnyPromise {
return AnyPromise.from(setProfilePictureURL(to: url, using: profileKey, on: server))
}
public static func setProfilePictureURL(to url: String?, using profileKey: Data, on server: String) -> Promise<Void> {
SNLog("Updating profile picture on server: \(server).")
var annotation: JSON = [ "type" : profilePictureType ]
if let url = url {
annotation["value"] = [ "profileKey" : profileKey.base64EncodedString(), "url" : url ]
}
let parameters: JSON = [ "annotations" : [ annotation ] ]
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let url = URL(string: "\(server)/users/me")!
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { _ in }.recover(on: DispatchQueue.global(qos: .default)) { error in
SNLog("Couldn't update profile picture due to error: \(error).")
throw error
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Joining & Leaving
@objc(getInfoForChannelWithID:onServer:)
public static func objc_getInfo(for channel: UInt64, on server: String) -> AnyPromise {
return AnyPromise.from(getInfo(for: channel, on: server))
}
public static func getInfo(for channel: UInt64, on server: String) -> Promise<OpenGroupInfo> {
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<OpenGroupInfo> in
let url = URL(string: "\(server)/channels/\(channel)?include_annotations=1")!
let request = TSRequest(url: url)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in
guard let data = json["data"] as? JSON,
let annotations = data["annotations"] as? [JSON],
let annotation = annotations.first,
let info = annotation["value"] as? JSON,
let displayName = info["name"] as? String,
let profilePictureURL = info["avatar"] as? String,
let countInfo = data["counts"] as? JSON,
let memberCount = countInfo["subscribers"] as? Int else {
SNLog("Couldn't parse info for open group channel with ID: \(channel) on server: \(server) from: \(json).")
throw Error.parsingFailed
}
let storage = SNMessagingKitConfiguration.shared.storage
storage.writeSync { transaction in
storage.setUserCount(to: memberCount, forOpenGroupWithID: "\(server).\(channel)", using: transaction)
}
let openGroupInfo = OpenGroupInfo(displayName: displayName, profilePictureURL: profilePictureURL, memberCount: memberCount)
OpenGroupAPI.updateProfileIfNeeded(for: channel, on: server, from: openGroupInfo)
return openGroupInfo
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
public static func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: OpenGroupInfo) {
let openGroupID = "\(server).\(channel)"
SNMessagingKitConfiguration.shared.storage.write { transaction in
let transaction = transaction as! YapDatabaseReadWriteTransaction
// Update user count
Storage.shared.setUserCount(to: info.memberCount, forOpenGroupWithID: openGroupID, using: transaction)
let thread = TSGroupThread.getOrCreateThread(withGroupId: openGroupID.data(using: .utf8)!, groupType: .openGroup, transaction: transaction)
// Update display name if needed
let model = thread.groupModel
if model.groupName != info.displayName {
let newGroupModel = TSGroupModel(title: info.displayName, memberIds: model.groupMemberIds, image: model.groupImage, groupId: model.groupId, groupType: model.groupType, adminIds: model.groupAdminIds)
thread.groupModel = newGroupModel
thread.save(with: transaction)
}
// Download and update profile picture if needed
let oldProfilePictureURL = Storage.shared.getProfilePictureURL(forOpenGroupWithID: openGroupID)
if oldProfilePictureURL != info.profilePictureURL || model.groupImage == nil {
Storage.shared.setProfilePictureURL(to: info.profilePictureURL, forOpenGroupWithID: openGroupID, using: transaction)
if let profilePictureURL = info.profilePictureURL {
var sanitizedServerURL = server
while sanitizedServerURL.hasSuffix("/") { sanitizedServerURL.removeLast() }
var sanitizedProfilePictureURL = profilePictureURL
while sanitizedProfilePictureURL.hasPrefix("/") { sanitizedProfilePictureURL.removeFirst() }
let url = "\(sanitizedServerURL)/\(sanitizedProfilePictureURL)"
FileServerAPI.downloadAttachment(from: url).map2 { rawData in
let attachmentStream: TSAttachmentStream
let data: Data
if let rawImage = UIImage(data: rawData), let jpegData = rawImage.jpegData(compressionQuality: 0.8) {
data = jpegData
} else {
data = rawData
}
attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(data.count), sourceFilename: nil, caption: nil, albumMessageId: nil)
try attachmentStream.write(data)
thread.updateAvatar(with: attachmentStream)
}
}
}
}
}
public static func join(_ channel: UInt64, on server: String) -> Promise<Void> {
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let url = URL(string: "\(server)/channels/\(channel)/subscribe")!
let request = TSRequest(url: url, method: "POST", parameters: [:])
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).done(on: DispatchQueue.global(qos: .default)) { _ -> Void in
SNLog("Joined channel with ID: \(channel) on server: \(server).")
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
public static func leave(_ channel: UInt64, on server: String) -> Promise<Void> {
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Void> in
let url = URL(string: "\(server)/channels/\(channel)/subscribe")!
let request = TSRequest(url: url, method: "DELETE", parameters: [:])
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).done(on: DispatchQueue.global(qos: .default)) { _ -> Void in
SNLog("Left channel with ID: \(channel) on server: \(server).")
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Reporting
@objc(reportMessageWithID:inChannel:onServer:)
public static func objc_reportMessageWithID(_ messageID: UInt64, in channel: UInt64, on server: String) -> AnyPromise {
return AnyPromise.from(reportMessageWithID(messageID, in: channel, on: server))
}
public static func reportMessageWithID(_ messageID: UInt64, in channel: UInt64, on server: String) -> Promise<Void> {
let url = URL(string: "\(server)/loki/v1/channels/\(channel)/messages/\(messageID)/report")!
let request = TSRequest(url: url, method: "POST", parameters: [:])
// Only used for the Loki Public Chat which doesn't require authentication
return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { _ in }
}
}
// MARK: Moderators
public static func getModerators(for channel: UInt64, on server: String) -> Promise<Set<String>> {
return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<Set<String>> in
let url = URL(string: "\(server)/loki/v1/channel/\(channel)/get_moderators")!
let request = TSRequest(url: url)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in
guard let moderators = json["moderators"] as? [String] else {
SNLog("Couldn't parse moderators for open group channel with ID: \(channel) on server: \(server) from: \(json).")
throw Error.parsingFailed
}
let moderatorsAsSet = Set(moderators);
if self.moderators.keys.contains(server) {
self.moderators[server]![channel] = moderatorsAsSet
} else {
self.moderators[server] = [ channel : moderatorsAsSet ]
}
return moderatorsAsSet
}
}
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
@objc(isUserModerator:forChannel:onServer:)
public static func isUserModerator(_ hexEncodedPublicString: String, for channel: UInt64, on server: String) -> Bool {
return moderators[server]?[channel]?.contains(hexEncodedPublicString) ?? false
}
}
// MARK: Error Handling
internal extension Promise {
func handlingInvalidAuthTokenIfNeeded(for server: String) -> Promise<T> {
return recover(on: DispatchQueue.global(qos: .userInitiated)) { error -> Promise<T> in
if case OnionRequestAPI.Error.httpRequestFailedAtDestination(let statusCode, _) = error, statusCode == 401 || statusCode == 403 {
SNLog("Auth token for: \(server) expired; dropping it.")
let storage = SNMessagingKitConfiguration.shared.storage
storage.writeSync { transaction in
storage.removeAuthToken(for: server, using: transaction)
}
}
throw error
}
}
}

View File

@ -1,6 +0,0 @@
public struct OpenGroupInfo {
public let displayName: String
public let profilePictureURL: String?
public let memberCount: Int
}

View File

@ -1,101 +0,0 @@
import PromiseKit
@objc(SNOpenGroupManager)
public final class OpenGroupManager : NSObject {
private var pollers: [String:OpenGroupPoller] = [:]
private var isPolling = false
// MARK: Error
public enum Error : LocalizedError {
case invalidURL
public var errorDescription: String? {
switch self {
case .invalidURL: return "Invalid URL."
}
}
}
// MARK: Initialization
@objc public static let shared = OpenGroupManager()
private override init() { }
// MARK: Polling
@objc public func startPolling() {
guard !isPolling else { return }
isPolling = true
let openGroups = Storage.shared.getAllUserOpenGroups()
for (_, openGroup) in openGroups {
if let poller = pollers[openGroup.id] { poller.stop() } // Should never occur
let poller = OpenGroupPoller(for: openGroup)
poller.startIfNeeded()
pollers[openGroup.id] = poller
}
}
@objc public func stopPolling() {
pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
pollers.removeAll()
}
// MARK: Adding & Removing
public func add(with url: String, using transaction: Any) -> Promise<Void> {
let storage = Storage.shared
guard let url = URL(string: url), let scheme = url.scheme, scheme == "https", url.host != nil else {
return Promise(error: Error.invalidURL)
}
let channel: UInt64 = 1
let server = url.absoluteString
let userPublicKey = getUserHexEncodedPublicKey()
let name = storage.getUser()?.name
let profilePictureURL = storage.getUser()?.profilePictureURL
let profileKey = storage.getUser()?.profilePictureEncryptionKey?.keyData
storage.removeLastMessageServerID(for: channel, on: server, using: transaction)
storage.removeLastDeletionServerID(for: channel, on: server, using: transaction)
return OpenGroupAPI.getInfo(for: channel, on: server).done { info in
let openGroup = OpenGroup(channel: channel, server: server, displayName: info.displayName, isDeletable: true)!
let groupID = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
let model = TSGroupModel(title: openGroup.displayName, memberIds: [ userPublicKey ], image: nil, groupId: groupID, groupType: .openGroup, adminIds: [])
storage.write(with: { transaction in
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction as! YapDatabaseReadWriteTransaction)
storage.setOpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
}, completion: {
let _ = OpenGroupAPI.setDisplayName(to: name, on: server)
if let profilePictureURL = profilePictureURL, let profileKey = profileKey {
let _ = OpenGroupAPI.setProfilePictureURL(to: profilePictureURL, using: profileKey, on: server)
}
let _ = OpenGroupAPI.join(channel, on: server)
if let poller = OpenGroupManager.shared.pollers[openGroup.id] {
poller.stop()
OpenGroupManager.shared.pollers[openGroup.id] = nil
}
let poller = OpenGroupPoller(for: openGroup)
poller.startIfNeeded()
OpenGroupManager.shared.pollers[openGroup.id] = poller
})
}
}
public func delete(_ openGroup: OpenGroup, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
if let poller = pollers[openGroup.id] {
poller.stop()
pollers[openGroup.id] = nil
}
var messageIDs: Set<String> = []
var messageTimestamps: Set<UInt64> = []
thread.enumerateInteractions(with: transaction) { interaction, _ in
messageIDs.insert(interaction.uniqueId!)
messageTimestamps.insert(interaction.timestamp)
}
SNMessagingKitConfiguration.shared.storage.updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, using: transaction)
Storage.shared.removeReceivedMessageTimestamps(messageTimestamps, using: transaction)
Storage.shared.removeLastMessageServerID(for: openGroup.channel, on: openGroup.server, using: transaction)
Storage.shared.removeLastDeletionServerID(for: openGroup.channel, on: openGroup.server, using: transaction)
let _ = OpenGroupAPI.leave(openGroup.channel, on: openGroup.server)
Storage.shared.removeOpenGroupPublicKey(for: openGroup.server, using: transaction)
thread.removeAllThreadInteractions(with: transaction)
thread.remove(with: transaction)
Storage.shared.removeOpenGroup(for: thread.uniqueId!, using: transaction)
}
}

View File

@ -1,82 +0,0 @@
internal extension OpenGroupMessage {
static func from(_ message: VisibleMessage, for server: String, using transaction: YapDatabaseReadWriteTransaction) -> OpenGroupMessage? {
let storage = SNMessagingKitConfiguration.shared.storage
guard let userPublicKey = storage.getUserPublicKey() else { return nil }
var attachmentIDs = message.attachmentIDs
// Validation
guard message.isValid else { return nil } // Should be valid at this point
// Quote
let quote: OpenGroupMessage.Quote? = {
if let quote = message.quote {
guard quote.isValid else { return nil }
let quotedMessageBody = quote.text ?? String(quote.timestamp!) // The back-end doesn't accept messages without a body so we use this as a workaround
if let quotedAttachmentID = quote.attachmentID, let index = attachmentIDs.firstIndex(of: quotedAttachmentID) {
attachmentIDs.remove(at: index)
}
// FIXME: For some reason the server always returns a 500 if quotedMessageServerID is set...
return OpenGroupMessage.Quote(quotedMessageTimestamp: quote.timestamp!, quoteePublicKey: quote.publicKey!, quotedMessageBody: quotedMessageBody, quotedMessageServerID: nil)
} else {
return nil
}
}()
// Message
let name = storage.getUser()?.name ?? "Anonymous"
let body = message.text ?? String(message.sentTimestamp!) // The back-end doesn't accept messages without a body so we use this as a workaround
let result = OpenGroupMessage(serverID: nil, senderPublicKey: userPublicKey, displayName: name, profilePicture: nil, body: body,
type: OpenGroupAPI.openGroupMessageType, timestamp: message.sentTimestamp!, quote: quote, attachments: [], signature: nil, serverTimestamp: 0)
// Link preview
if let linkPreview = message.linkPreview {
guard linkPreview.isValid, let attachmentID = linkPreview.attachmentID,
let attachment = TSAttachment.fetch(uniqueId: attachmentID, transaction: transaction) as? TSAttachmentStream else { return nil }
if let index = attachmentIDs.firstIndex(of: attachmentID) {
attachmentIDs.remove(at: index)
}
let fileName = attachment.sourceFilename ?? UUID().uuidString
let width = attachment.shouldHaveImageSize() ? attachment.imageSize().width : 0
let height = attachment.shouldHaveImageSize() ? attachment.imageSize().height : 0
let openGroupLinkPreview = OpenGroupMessage.Attachment(
kind: .linkPreview,
server: server,
serverID: attachment.serverId,
contentType: attachment.contentType,
size: UInt(attachment.byteCount),
fileName: fileName,
flags: 0,
width: UInt(width),
height: UInt(height),
caption: attachment.caption,
url: attachment.downloadURL,
linkPreviewURL: linkPreview.url,
linkPreviewTitle: linkPreview.title
)
result.attachments.append(openGroupLinkPreview)
}
// Attachments
let attachments: [OpenGroupMessage.Attachment] = attachmentIDs.compactMap { attachmentID in
guard let attachment = TSAttachment.fetch(uniqueId: attachmentID, transaction: transaction) as? TSAttachmentStream else { return nil } // Should never occur
let fileName = attachment.sourceFilename ?? UUID().uuidString
let width = attachment.shouldHaveImageSize() ? attachment.imageSize().width : 0
let height = attachment.shouldHaveImageSize() ? attachment.imageSize().height : 0
return OpenGroupMessage.Attachment(
kind: .attachment,
server: server,
serverID: attachment.serverId,
contentType: attachment.contentType,
size: UInt(attachment.byteCount),
fileName: fileName,
flags: 0,
width: UInt(width),
height: UInt(height),
caption: attachment.caption,
url: attachment.downloadURL,
linkPreviewURL: nil,
linkPreviewTitle: nil
)
}
result.attachments += attachments
// Return
return result
}
}

View File

@ -1,196 +0,0 @@
import PromiseKit
import Curve25519Kit
import SessionUtilitiesKit
@objc(SNOpenGroupMessage)
public final class OpenGroupMessage : NSObject {
public let serverID: UInt64?
public let senderPublicKey: String
public let displayName: String
public let profilePicture: ProfilePicture?
public let body: String
/// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970.
public let timestamp: UInt64
public let type: String
public let quote: Quote?
public var attachments: [Attachment] = []
public let signature: Signature?
/// - Note: Used for sorting.
public let serverTimestamp: UInt64
@objc(serverID)
public var objc_serverID: UInt64 { return serverID ?? 0 }
// MARK: Settings
private let signatureVersion: UInt64 = 1
private let attachmentType = "net.app.core.oembed"
// MARK: Types
public struct ProfilePicture {
public let profileKey: Data
public let url: String
}
public struct Quote {
public let quotedMessageTimestamp: UInt64
public let quoteePublicKey: String
public let quotedMessageBody: String
public let quotedMessageServerID: UInt64?
}
public struct Attachment {
public let kind: Kind
public let server: String
public let serverID: UInt64
public let contentType: String
public let size: UInt
public let fileName: String
public let flags: UInt
public let width: UInt
public let height: UInt
public let caption: String?
public let url: String
/// Guaranteed to be non-`nil` if `kind` is `linkPreview`
public let linkPreviewURL: String?
/// Guaranteed to be non-`nil` if `kind` is `linkPreview`
public let linkPreviewTitle: String?
public enum Kind : String { case attachment, linkPreview = "preview" }
public var dotNETType: String {
if contentType.hasPrefix("image") {
return "photo"
} else if contentType.hasPrefix("video") {
return "video"
} else if contentType.hasPrefix("audio") {
return "audio"
} else {
return "other"
}
}
}
public struct Signature {
public let data: Data
public let version: UInt64
}
// MARK: Initialization
public init(serverID: UInt64?, senderPublicKey: String, displayName: String, profilePicture: ProfilePicture?, body: String,
type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?, serverTimestamp: UInt64) {
self.serverID = serverID
self.senderPublicKey = senderPublicKey
self.displayName = displayName
self.profilePicture = profilePicture
self.body = body
self.type = type
self.timestamp = timestamp
self.quote = quote
self.attachments = attachments
self.signature = signature
self.serverTimestamp = serverTimestamp
super.init()
}
@objc public convenience init(senderPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64,
quotedMessageTimestamp: UInt64, quoteePublicKey: String?, quotedMessageBody: String, quotedMessageServerID: UInt64,
signatureData: Data?, signatureVersion: UInt64, serverTimestamp: UInt64) {
let quote: Quote?
if quotedMessageTimestamp != 0, let quoteeHexEncodedPublicKey = quoteePublicKey {
let quotedMessageServerID = (quotedMessageServerID != 0) ? quotedMessageServerID : nil
quote = Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteePublicKey: quoteeHexEncodedPublicKey, quotedMessageBody: quotedMessageBody, quotedMessageServerID: quotedMessageServerID)
} else {
quote = nil
}
let signature: Signature?
if let signatureData = signatureData, signatureVersion != 0 {
signature = Signature(data: signatureData, version: signatureVersion)
} else {
signature = nil
}
self.init(serverID: nil, senderPublicKey: senderPublicKey, displayName: displayName, profilePicture: nil, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature, serverTimestamp: serverTimestamp)
}
// MARK: Crypto
internal func sign(with privateKey: Data) -> OpenGroupMessage? {
guard let data = getValidationData(for: signatureVersion) else {
SNLog("Failed to sign open group message.")
return nil
}
let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair()!
guard let signatureData = try? Ed25519.sign(data, with: userKeyPair) else {
SNLog("Failed to sign open group message.")
return nil
}
let signature = Signature(data: signatureData, version: signatureVersion)
return OpenGroupMessage(serverID: serverID, senderPublicKey: senderPublicKey, displayName: displayName, profilePicture: profilePicture, body: body, type: type, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature, serverTimestamp: serverTimestamp)
}
internal func hasValidSignature() -> Bool {
guard let signature = signature else { return false }
guard let data = getValidationData(for: signature.version) else { return false }
let publicKey = Data(hex: self.senderPublicKey.removing05PrefixIfNeeded())
return (try? Ed25519.verifySignature(signature.data, publicKey: publicKey, data: data)) ?? false
}
// MARK: JSON
internal func toJSON() -> JSON {
var value: JSON = [ "timestamp" : timestamp ]
if let quote = quote {
let quoteAsJSON: JSON = [ "id" : quote.quotedMessageTimestamp, "author" : quote.quoteePublicKey, "text" : quote.quotedMessageBody ]
value["quote"] = quoteAsJSON
}
if let signature = signature {
value["sig"] = signature.data.toHexString()
value["sigver"] = signature.version
}
if let profilePicture = profilePicture {
value["avatar"] = profilePicture;
}
let annotation: JSON = [ "type" : type, "value" : value ]
let attachmentAnnotations: [JSON] = attachments.map { attachment in
var attachmentValue: JSON = [
// Fields required by the .NET API
"version" : 1, "type" : attachment.dotNETType,
// Custom fields
"lokiType" : attachment.kind.rawValue, "server" : attachment.server, "id" : attachment.serverID, "contentType" : attachment.contentType, "size" : attachment.size, "fileName" : attachment.fileName, "width" : attachment.width, "height" : attachment.height, "url" : attachment.url
]
if let caption = attachment.caption {
attachmentValue["caption"] = caption
}
if let linkPreviewURL = attachment.linkPreviewURL {
attachmentValue["linkPreviewUrl"] = linkPreviewURL
}
if let linkPreviewTitle = attachment.linkPreviewTitle {
attachmentValue["linkPreviewTitle"] = linkPreviewTitle
}
return [ "type" : attachmentType, "value" : attachmentValue ]
}
var result: JSON = [ "text" : body, "annotations": [ annotation ] + attachmentAnnotations ]
if let quotedMessageServerID = quote?.quotedMessageServerID {
result["reply_to"] = quotedMessageServerID
}
return result
}
// MARK: Convenience
@objc public func addAttachment(kind: String, server: String, serverID: UInt64, contentType: String, size: UInt,
fileName: String, flags: UInt, width: UInt, height: UInt, caption: String?, url: String, linkPreviewURL: String?, linkPreviewTitle: String?) {
guard let kind = Attachment.Kind(rawValue: kind) else { preconditionFailure() }
let attachment = Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle)
attachments.append(attachment)
}
private func getValidationData(for signatureVersion: UInt64) -> Data? {
var string = "\(body.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))\(timestamp)"
if let quote = quote {
string += "\(quote.quotedMessageTimestamp)\(quote.quoteePublicKey)\(quote.quotedMessageBody.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))"
if let quotedMessageServerID = quote.quotedMessageServerID {
string += "\(quotedMessageServerID)"
}
}
string += attachments.sorted { $0.serverID < $1.serverID }.map { "\($0.serverID)" }.joined(separator: "")
string += "\(signatureVersion)"
return string.data(using: String.Encoding.utf8)
}
}

View File

@ -30,11 +30,10 @@ public final class MentionsManager : NSObject {
guard let cache = userPublicKeyCache[threadID] else { return [] }
var candidates: [Mention] = []
// Gather candidates
let openGroup = Storage.shared.getOpenGroup(for: threadID)
let openGroupV2 = Storage.shared.getV2OpenGroup(for: threadID)
storage.dbReadConnection.read { transaction in
candidates = cache.compactMap { publicKey in
let context: Contact.Context = (openGroupV2 != nil || openGroup != nil) ? .openGroup : .regular
let context: Contact.Context = (openGroupV2 != nil) ? .openGroup : .regular
let displayNameOrNil = Storage.shared.getContact(with: publicKey)?.displayName(for: context)
guard let displayName = displayNameOrNil else { return nil }
guard !displayName.hasPrefix("Anonymous") else { return nil }

View File

@ -216,8 +216,6 @@ extension MessageReceiver {
for openGroupURL in message.openGroups {
if let (room, server, publicKey) = OpenGroupManagerV2.parseV2OpenGroup(from: openGroupURL) {
OpenGroupManagerV2.shared.add(room: room, server: server, publicKey: publicKey, using: transaction).retainUntilComplete()
} else {
OpenGroupManager.shared.add(with: openGroupURL, using: transaction).retainUntilComplete()
}
}
}
@ -244,16 +242,10 @@ extension MessageReceiver {
message.attachmentIDs = attachmentIDs
var attachmentsToDownload = attachmentIDs
// Update profile if needed
if let newProfile = message.profile {
if let profile = message.profile {
let sessionID = message.sender!
updateProfileIfNeeded(publicKey: sessionID, name: newProfile.displayName, profilePictureURL: newProfile.profilePictureURL,
profileKey: given(newProfile.profileKey) { OWSAES256Key(data: $0)! }, sentTimestamp: message.sentTimestamp!, transaction: transaction)
if let rawDisplayName = newProfile.displayName, let openGroupID = openGroupID {
let endIndex = sessionID.endIndex
let cutoffIndex = sessionID.index(endIndex, offsetBy: -8)
let displayName = "\(rawDisplayName) (...\(sessionID[cutoffIndex..<endIndex]))"
Storage.shared.setOpenGroupDisplayName(to: displayName, for: sessionID, inOpenGroupWithID: openGroupID, using: transaction)
}
updateProfileIfNeeded(publicKey: sessionID, name: profile.displayName, profilePictureURL: profile.profilePictureURL,
profileKey: given(profile.profileKey) { OWSAES256Key(data: $0)! }, sentTimestamp: message.sentTimestamp!, transaction: transaction)
}
// Get or create thread
guard let threadID = storage.getOrCreateThread(for: message.syncTarget ?? message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: openGroupID, using: transaction) else { throw Error.noThread }

View File

@ -281,67 +281,41 @@ public final class MessageSender : NSObject {
#endif
}
guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise }
// There's quite a bit of overlap between the two clauses of this if statement for now, but that'll be fixed
// when we remove support for V1 open groups
if case .openGroup(let channel, let server) = destination {
// The back-end doesn't accept messages without a body so we use this as a workaround
if message.text?.isEmpty != false {
message.text = String(message.sentTimestamp!)
}
// Convert the message to an open group message
guard let openGroupMessage = OpenGroupMessage.from(message, for: server, using: transaction) else { handleFailure(with: Error.invalidMessage, using: transaction); return promise }
// Send the result
OpenGroupAPI.sendMessage(openGroupMessage, to: channel, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in
message.openGroupServerMessageID = openGroupMessage.serverID
storage.write(with: { transaction in
MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction)
seal.fulfill(())
}, completion: { })
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
storage.write(with: { transaction in
handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction)
}, completion: { })
}
// Return
return promise
} else if case .openGroupV2(let room, let server) = destination {
// Attach the user's profile
guard let name = storage.getUser()?.name else { handleFailure(with: Error.noUsername, using: transaction); return promise }
if let profileKey = storage.getUser()?.profilePictureEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL {
message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL)
} else {
message.profile = VisibleMessage.Profile(displayName: name)
}
// Convert it to protobuf
guard let proto = message.toProto(using: transaction) else { handleFailure(with: Error.protoConversionFailed, using: transaction); return promise }
// Serialize the protobuf
let plaintext: Data
do {
plaintext = (try proto.serializedData() as NSData).paddedMessageBody()
} catch {
SNLog("Couldn't serialize proto due to error: \(error).")
handleFailure(with: error, using: transaction)
return promise
}
// Send the result
let openGroupMessage = OpenGroupMessageV2(serverID: nil, sender: nil, sentTimestamp: message.sentTimestamp!,
base64EncodedData: plaintext.base64EncodedString(), base64EncodedSignature: nil)
OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in
message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) }
storage.write(with: { transaction in
MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction)
seal.fulfill(())
}, completion: { })
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
storage.write(with: { transaction in
handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction)
}, completion: { })
}
// Return
return promise
// Attach the user's profile
guard let name = storage.getUser()?.name else { handleFailure(with: Error.noUsername, using: transaction); return promise }
if let profileKey = storage.getUser()?.profilePictureEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL {
message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL)
} else {
preconditionFailure()
message.profile = VisibleMessage.Profile(displayName: name)
}
// Convert it to protobuf
guard let proto = message.toProto(using: transaction) else { handleFailure(with: Error.protoConversionFailed, using: transaction); return promise }
// Serialize the protobuf
let plaintext: Data
do {
plaintext = (try proto.serializedData() as NSData).paddedMessageBody()
} catch {
SNLog("Couldn't serialize proto due to error: \(error).")
handleFailure(with: error, using: transaction)
return promise
}
// Send the result
guard case .openGroupV2(let room, let server) = destination else { preconditionFailure() }
let openGroupMessage = OpenGroupMessageV2(serverID: nil, sender: nil, sentTimestamp: message.sentTimestamp!,
base64EncodedData: plaintext.base64EncodedString(), base64EncodedSignature: nil)
OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in
message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) }
storage.write(with: { transaction in
MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction)
seal.fulfill(())
}, completion: { })
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
storage.write(with: { transaction in
handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction)
}, completion: { })
}
// Return
return promise
}
// MARK: Success & Failure Handling

View File

@ -1,198 +0,0 @@
import PromiseKit
@objc(LKOpenGroupPoller)
public final class OpenGroupPoller : NSObject {
private let openGroup: OpenGroup
private var pollForNewMessagesTimer: Timer? = nil
private var pollForDeletedMessagesTimer: Timer? = nil
private var pollForModeratorsTimer: Timer? = nil
private var hasStarted = false
private var isPolling = false
private var isMainAppAndActive: Bool {
var isMainAppAndActive = false
if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") {
isMainAppAndActive = sharedUserDefaults.bool(forKey: "isMainAppActive")
}
return isMainAppAndActive
}
// MARK: Settings
private let pollForNewMessagesInterval: TimeInterval = 20
private let pollForDeletedMessagesInterval: TimeInterval = 30
private let pollForModeratorsInterval: TimeInterval = 10 * 60
// MARK: Lifecycle
@objc(initForOpenGroup:)
public init(for openGroup: OpenGroup) {
self.openGroup = openGroup
super.init()
}
@objc public func startIfNeeded() {
guard !hasStarted else { return }
guard isMainAppAndActive else { stop(); return }
DispatchQueue.main.async { [weak self] in // Timers don't do well on background queues
guard let strongSelf = self else { return }
strongSelf.pollForNewMessagesTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForNewMessagesInterval, repeats: true) { _ in self?.pollForNewMessages() }
strongSelf.pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForDeletedMessagesInterval, repeats: true) { _ in self?.pollForDeletedMessages() }
strongSelf.pollForModeratorsTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForModeratorsInterval, repeats: true) { _ in self?.pollForModerators() }
// Perform initial updates
strongSelf.pollForNewMessages()
strongSelf.pollForDeletedMessages()
strongSelf.pollForModerators()
strongSelf.hasStarted = true
}
}
@objc public func stop() {
pollForNewMessagesTimer?.invalidate()
pollForDeletedMessagesTimer?.invalidate()
pollForModeratorsTimer?.invalidate()
hasStarted = false
}
// MARK: Polling
@objc(pollForNewMessages)
public func objc_pollForNewMessages() -> AnyPromise {
AnyPromise.from(pollForNewMessages())
}
@discardableResult
public func pollForNewMessages() -> Promise<Void> {
guard isMainAppAndActive else { stop(); return Promise.value(()) }
return pollForNewMessages(isBackgroundPoll: false)
}
@discardableResult
public func pollForNewMessages(isBackgroundPoll: Bool) -> Promise<Void> {
guard !self.isPolling else { return Promise.value(()) }
self.isPolling = true
let openGroup = self.openGroup
let (promise, seal) = Promise<Void>.pending()
promise.retainUntilComplete()
OpenGroupAPI.getMessages(for: openGroup.channel, on: openGroup.server).done(on: DispatchQueue.global(qos: .default)) { messages in
self.isPolling = false
// Sorting the messages by timestamp before importing them fixes an issue where messages that quote older messages can't find those older messages
messages.sorted { $0.serverTimestamp < $1.serverTimestamp }.forEach { message in
let senderPublicKey = message.senderPublicKey
let wasSentByCurrentUser = (senderPublicKey == getUserHexEncodedPublicKey())
func generateDisplayName(from rawDisplayName: String) -> String {
let endIndex = senderPublicKey.endIndex
let cutoffIndex = senderPublicKey.index(endIndex, offsetBy: -8)
return "\(rawDisplayName) (...\(senderPublicKey[cutoffIndex..<endIndex]))"
}
let senderDisplayName = Storage.shared.getContact(with: senderPublicKey)?.displayName(for: .openGroup)
?? generateDisplayName(from: NSLocalizedString("Anonymous", comment: ""))
let id = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
// Main message
let dataMessageProto = SNProtoDataMessage.builder()
let body = (message.body == message.timestamp.description) ? "" : message.body // The back-end doesn't accept messages without a body so we use this as a workaround
dataMessageProto.setBody(body)
dataMessageProto.setTimestamp(message.timestamp)
// Attachments
let attachmentProtos: [SNProtoAttachmentPointer] = message.attachments.compactMap { attachment in
guard attachment.kind == .attachment else { return nil }
let attachmentProto = SNProtoAttachmentPointer.builder(id: attachment.serverID)
attachmentProto.setContentType(attachment.contentType)
attachmentProto.setSize(UInt32(attachment.size))
attachmentProto.setFileName(attachment.fileName)
attachmentProto.setFlags(UInt32(attachment.flags))
attachmentProto.setWidth(UInt32(attachment.width))
attachmentProto.setHeight(UInt32(attachment.height))
if let caption = attachment.caption { attachmentProto.setCaption(caption) }
attachmentProto.setUrl(attachment.url)
return try! attachmentProto.build()
}
dataMessageProto.setAttachments(attachmentProtos)
// Link preview
if let linkPreview = message.attachments.first(where: { $0.kind == .linkPreview }) {
let linkPreviewProto = SNProtoDataMessagePreview.builder(url: linkPreview.linkPreviewURL!)
linkPreviewProto.setTitle(linkPreview.linkPreviewTitle!)
let attachmentProto = SNProtoAttachmentPointer.builder(id: linkPreview.serverID)
attachmentProto.setContentType(linkPreview.contentType)
attachmentProto.setSize(UInt32(linkPreview.size))
attachmentProto.setFileName(linkPreview.fileName)
attachmentProto.setFlags(UInt32(linkPreview.flags))
attachmentProto.setWidth(UInt32(linkPreview.width))
attachmentProto.setHeight(UInt32(linkPreview.height))
if let caption = linkPreview.caption { attachmentProto.setCaption(caption) }
attachmentProto.setUrl(linkPreview.url)
linkPreviewProto.setImage(try! attachmentProto.build())
dataMessageProto.setPreview([ try! linkPreviewProto.build() ])
}
// Quote
if let quote = message.quote {
let quoteProto = SNProtoDataMessageQuote.builder(id: quote.quotedMessageTimestamp, author: quote.quoteePublicKey)
if quote.quotedMessageBody != String(quote.quotedMessageTimestamp) { quoteProto.setText(quote.quotedMessageBody) }
dataMessageProto.setQuote(try! quoteProto.build())
}
// Profile
let profileProto = SNProtoDataMessageLokiProfile.builder()
profileProto.setDisplayName(message.displayName)
if let profilePicture = message.profilePicture {
profileProto.setProfilePicture(profilePicture.url)
dataMessageProto.setProfileKey(profilePicture.profileKey)
}
dataMessageProto.setProfile(try! profileProto.build())
// Signal group context
let groupProto = SNProtoGroupContext.builder(id: id, type: .deliver)
groupProto.setName(openGroup.displayName)
dataMessageProto.setGroup(try! groupProto.build())
// Sync target
if wasSentByCurrentUser {
dataMessageProto.setSyncTarget(openGroup.id)
}
// Content
let content = SNProtoContent.builder()
content.setDataMessage(try! dataMessageProto.build())
// Envelope
let envelope = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: message.timestamp)
envelope.setSource(senderPublicKey)
envelope.setSourceDevice(1)
envelope.setContent(try! content.build().serializedData())
envelope.setServerTimestamp(message.serverTimestamp)
SNMessagingKitConfiguration.shared.storage.write { transaction in
Storage.shared.setOpenGroupDisplayName(to: senderDisplayName, for: senderPublicKey, inOpenGroupWithID: openGroup.id, using: transaction)
let messageServerID = message.serverID
let job = MessageReceiveJob(data: try! envelope.buildSerializedData(), openGroupMessageServerID: messageServerID, openGroupID: openGroup.id, isBackgroundPoll: isBackgroundPoll)
if isBackgroundPoll {
job.execute().done(on: DispatchQueue.global(qos: .userInitiated)) {
seal.fulfill(())
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { _ in
seal.fulfill(()) // The promise is just used to keep track of when we're done
}
} else {
SessionMessagingKit.JobQueue.shared.add(job, using: transaction)
seal.fulfill(())
}
}
}
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { _ in
seal.fulfill(()) // The promise is just used to keep track of when we're done
}.retainUntilComplete()
return promise
}
private func pollForDeletedMessages() {
let openGroup = self.openGroup
let storage = SNMessagingKitConfiguration.shared.storage
OpenGroupAPI.getDeletedMessageServerIDs(for: openGroup.channel, on: openGroup.server).done(on: DispatchQueue.global(qos: .default)) { deletedMessageServerIDs in
storage.write { transaction in
let transaction = transaction as! YapDatabaseReadWriteTransaction
guard let threadID = storage.getThreadID(for: openGroup.id),
let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { return }
var messagesToRemove: [TSMessage] = []
thread.enumerateInteractions(with: transaction) { interaction, stop in
guard let message = interaction as? TSMessage, deletedMessageServerIDs.contains(message.openGroupServerMessageID) else { return }
messagesToRemove.append(message)
}
messagesToRemove.forEach { $0.remove(with: transaction) }
}
}.retainUntilComplete()
}
private func pollForModerators() {
OpenGroupAPI.getModerators(for: openGroup.channel, on: openGroup.server).retainUntilComplete()
}
}

View File

@ -48,7 +48,6 @@ public protocol SessionMessagingKitStorageProtocol {
func getAllV2OpenGroups() -> [String:OpenGroupV2]
func getV2OpenGroup(for threadID: String) -> OpenGroupV2?
func v2GetThreadID(for v2OpenGroupID: String) -> String?
func getThreadID(for openGroupID: String) -> String?
func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any)
// MARK: - Open Group Public Keys
@ -71,8 +70,6 @@ public protocol SessionMessagingKitStorageProtocol {
// MARK: - Open Group Metadata
func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any)
func setOpenGroupDisplayName(to displayName: String, for publicKey: String, inOpenGroupWithID openGroupID: String, using transaction: Any)
func setLastProfilePictureUploadDate(_ date: Date) // Stored in user defaults so no transaction is needed
// MARK: - Message Handling
@ -88,23 +85,4 @@ public protocol SessionMessagingKitStorageProtocol {
func setAttachmentState(to state: TSAttachmentPointerState, for pointer: TSAttachmentPointer, associatedWith tsIncomingMessageID: String, using transaction: Any)
/// Also touches the associated message.
func persist(_ stream: TSAttachmentStream, associatedWith tsIncomingMessageID: String, using transaction: Any)
// MARK: - Deprecated
func getAuthToken(for server: String) -> String?
func setAuthToken(for server: String, to newValue: String, using transaction: Any)
func removeAuthToken(for server: String, using transaction: Any)
func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64?
func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any)
func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any)
func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64?
func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any)
func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any)
func getAllUserOpenGroups() -> [String:OpenGroup]
func getOpenGroup(for threadID: String) -> OpenGroup?
func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any)
}

View File

@ -1,226 +0,0 @@
import AFNetworking
import CryptoSwift
import PromiseKit
import SessionSnodeKit
import SessionUtilitiesKit
import SignalCoreKit
/// Base class for `FileServerAPI` and `OpenGroupAPI`.
public class DotNetAPI : NSObject {
// MARK: Settings
private static let attachmentType = "network.loki"
private static let maxRetryCount: UInt = 4
// MARK: Error
public enum Error : LocalizedError {
case generic
case invalidURL
case parsingFailed
case signingFailed
case encryptionFailed
case decryptionFailed
case maxFileSizeExceeded
internal var isRetryable: Bool {
return false
}
public var errorDescription: String? {
switch self {
case .generic: return "An error occurred."
case .invalidURL: return "Invalid URL."
case .parsingFailed: return "Invalid file server response."
case .signingFailed: return "Couldn't sign message."
case .encryptionFailed: return "Couldn't encrypt file."
case .decryptionFailed: return "Couldn't decrypt file."
case .maxFileSizeExceeded: return "Maximum file size exceeded."
}
}
}
// MARK: Lifecycle
override private init() { }
// MARK: Private API
private static func requestNewAuthToken(for server: String) -> Promise<String> {
SNLog("Requesting auth token for server: \(server).")
guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { return Promise(error: Error.generic) }
let queryParameters = "pubKey=\(userKeyPair.publicKey.toHexString())"
let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")!
let request = TSRequest(url: url)
let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise.value(FileServerAPI.publicKey)
: OpenGroupAPI.getOpenGroupServerPublicKey(for: server)
return serverPublicKeyPromise.then(on: DispatchQueue.global(qos: .userInitiated)) { serverPublicKey in
OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey)
}.map(on: DispatchQueue.global(qos: .userInitiated)) { json in
guard let base64EncodedChallenge = json["cipherText64"] as? String, let base64EncodedServerPublicKey = json["serverPubKey64"] as? String,
let challenge = Data(base64Encoded: base64EncodedChallenge), var serverPublicKey = Data(base64Encoded: base64EncodedServerPublicKey) else {
throw Error.parsingFailed
}
// Discard the "05" prefix if needed
if serverPublicKey.count == 33 {
let hexEncodedServerPublicKey = serverPublicKey.toHexString()
let startIndex = hexEncodedServerPublicKey.index(hexEncodedServerPublicKey.startIndex, offsetBy: 2)
serverPublicKey = Data(hex: String(hexEncodedServerPublicKey[startIndex..<hexEncodedServerPublicKey.endIndex]))
}
// The challenge is prefixed by the 16 bit IV
guard let tokenAsData = try? DiffieHellman.decrypt(challenge, publicKey: serverPublicKey, privateKey: userKeyPair.privateKey),
let token = String(bytes: tokenAsData, encoding: .utf8) else {
throw Error.decryptionFailed
}
return token
}
}
private static func submitAuthToken(_ token: String, for server: String) -> Promise<String> {
SNLog("Submitting auth token for server: \(server).")
let url = URL(string: "\(server)/loki/v1/submit_challenge")!
guard let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey() else { return Promise(error: Error.generic) }
let parameters = [ "pubKey" : userPublicKey, "token" : token ]
let request = TSRequest(url: url, method: "POST", parameters: parameters)
let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise.value(FileServerAPI.publicKey)
: OpenGroupAPI.getOpenGroupServerPublicKey(for: server)
return serverPublicKeyPromise.then(on: DispatchQueue.global(qos: .userInitiated)) { serverPublicKey in
OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey)
}.map(on: DispatchQueue.global(qos: .userInitiated)) { _ in token }
}
// MARK: Public API
public static func getAuthToken(for server: String) -> Promise<String> {
let storage = SNMessagingKitConfiguration.shared.storage
if let token = storage.getAuthToken(for: server) {
return Promise.value(token)
} else {
return requestNewAuthToken(for: server).then(on: DispatchQueue.global(qos: .userInitiated)) { submitAuthToken($0, for: server) }.map(on: DispatchQueue.global(qos: .userInitiated)) { token in
storage.writeSync { transaction in
storage.setAuthToken(for: server, to: token, using: transaction)
}
return token
}
}
}
@objc(downloadAttachmentFrom:)
public static func objc_downloadAttachment(from url: String) -> AnyPromise {
return AnyPromise.from(downloadAttachment(from: url))
}
public static func downloadAttachment(from urlAsString: String) -> Promise<Data> {
guard let url = URL(string: urlAsString) else { return Promise(error: Error.invalidURL) }
var host = "https://\(url.host!)"
let sanitizedURL: String
if FileServerAPI.fileStorageBucketURL.contains(host) {
sanitizedURL = urlAsString.replacingOccurrences(of: FileServerAPI.fileStorageBucketURL, with: "\(FileServerAPI.server)/loki/v1")
host = FileServerAPI.server
} else {
sanitizedURL = urlAsString.replacingOccurrences(of: host, with: "\(host)/loki/v1")
}
let request: NSMutableURLRequest
do {
request = try AFHTTPRequestSerializer().request(withMethod: "GET", urlString: sanitizedURL, parameters: nil)
} catch {
SNLog("Couldn't download attachment due to error: \(error).")
return Promise(error: error)
}
let serverPublicKeyPromise = FileServerAPI.server.contains(host) ? Promise.value(FileServerAPI.publicKey)
: OpenGroupAPI.getOpenGroupServerPublicKey(for: host)
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .userInitiated)) {
serverPublicKeyPromise.then(on: DispatchQueue.global(qos: .userInitiated)) { serverPublicKey in
return OnionRequestAPI.sendOnionRequest(request, to: host, using: serverPublicKey, isJSONRequired: false).map(on: DispatchQueue.global(qos: .userInitiated)) { json in
guard let body = json["result"] as? String, let data = Data(base64Encoded: body) else {
SNLog("Couldn't parse attachment from: \(json).")
throw Error.parsingFailed
}
return data
}
}
}
}
@objc(uploadAttachment:withID:toServer:)
public static func objc_uploadAttachment(_ attachment: TSAttachmentStream, with attachmentID: String, to server: String) -> AnyPromise {
return AnyPromise.from(uploadAttachment(attachment, with: attachmentID, to: server))
}
public static func uploadAttachment(_ attachment: TSAttachmentStream, with attachmentID: String, to server: String) -> Promise<Void> {
let isEncryptionRequired = (server == FileServerAPI.server)
return Promise<Void>() { seal in
func proceed(with token: String) {
// Get the attachment
let data: Data
guard let unencryptedAttachmentData = try? attachment.readDataFromFile() else {
SNLog("Couldn't read attachment from disk.")
return seal.reject(Error.generic)
}
// Encrypt the attachment if needed
if isEncryptionRequired {
var encryptionKey = NSData()
var digest = NSData()
guard let encryptedAttachmentData = Cryptography.encryptAttachmentData(unencryptedAttachmentData, shouldPad: false, outKey: &encryptionKey, outDigest: &digest) else {
SNLog("Couldn't encrypt attachment.")
return seal.reject(Error.encryptionFailed)
}
attachment.encryptionKey = encryptionKey as Data
attachment.digest = digest as Data
data = encryptedAttachmentData
} else {
data = unencryptedAttachmentData
}
// Check the file size if needed
SNLog("File size: \(data.count) bytes.")
if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier {
return seal.reject(Error.maxFileSizeExceeded)
}
// Create the request
let url = "\(server)/files"
let parameters: JSON = [ "type" : attachmentType, "Content-Type" : "application/binary" ]
var error: NSError?
let request = AFHTTPRequestSerializer().multipartFormRequest(withMethod: "POST", urlString: url, parameters: parameters, constructingBodyWith: { formData in
let uuid = UUID().uuidString
SNLog("File UUID: \(uuid).")
formData.appendPart(withFileData: data, name: "content", fileName: uuid, mimeType: "application/binary")
}, error: &error)
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if let error = error {
SNLog("Couldn't upload attachment due to error: \(error).")
return seal.reject(error)
}
// Send the request
let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise.value(FileServerAPI.publicKey)
: OpenGroupAPI.getOpenGroupServerPublicKey(for: server)
attachment.isUploaded = false
attachment.save()
let _ = serverPublicKeyPromise.then(on: DispatchQueue.global(qos: .userInitiated)) { serverPublicKey in
OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey)
}.done(on: DispatchQueue.global(qos: .userInitiated)) { json in
// Parse the server ID & download URL
guard let data = json["data"] as? JSON, let serverID = data["id"] as? UInt64, let downloadURL = data["url"] as? String else {
SNLog("Couldn't parse attachment from: \(json).")
return seal.reject(Error.parsingFailed)
}
// Update the attachment
attachment.serverId = serverID
attachment.isUploaded = true
attachment.downloadURL = downloadURL
attachment.save()
seal.fulfill(())
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
seal.reject(error)
}
}
if server == FileServerAPI.server {
DispatchQueue.global(qos: .userInitiated).async {
proceed(with: "loki") // Uploads to the Loki File Server shouldn't include any personally identifiable information so use a dummy auth token
}
} else {
getAuthToken(for: server).done(on: DispatchQueue.global(qos: .userInitiated)) { token in
proceed(with: token)
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
SNLog("Couldn't upload attachment due to error: \(error).")
seal.reject(error)
}
}
}
}
}

View File

@ -3,7 +3,7 @@
//
@objc
public protocol OWSProximityMonitoringManager: class {
public protocol OWSProximityMonitoringManager: AnyObject {
func add(lifetime: AnyObject)
func remove(lifetime: AnyObject)
}

View File

@ -3,6 +3,4 @@
public final class Features : NSObject {
public static let useOnionRequests = true
public static let useTestnet = false
@objc public static let useV2OpenGroups = true
@objc public static let useV2FileServer = true
}

View File

@ -7,11 +7,9 @@ public enum SNUserDefaults {
case hasViewedSeed
case hasSeenLinkPreviewSuggestion
case isUsingFullAPNs
case isMigratingToV2KeyPair
}
public enum Date : Swift.String {
case lastProfilePictureUpload
case lastConfigurationSync
case lastDisplayNameUpdate
case lastProfilePictureUpdate
@ -28,8 +26,6 @@ public enum SNUserDefaults {
public enum String : Swift.String {
case deviceToken
/// Just used for migration purposes.
case displayName
}
}

View File

@ -9,6 +9,6 @@ public final class Configuration : NSObject {
@objc public static func performMainSetup() {
SNMessagingKit.configure(storage: Storage.shared)
SNSnodeKit.configure(storage: Storage.shared)
SNUtilitiesKit.configure(owsPrimaryStorage: OWSPrimaryStorage.shared(), maxFileSize: UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier))
SNUtilitiesKit.configure(owsPrimaryStorage: OWSPrimaryStorage.shared(), maxFileSize: UInt(Double(FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier))
}
}

View File

@ -27,8 +27,6 @@ extension ConfigurationMessage {
case .openGroup:
if let v2OpenGroup = storage.getV2OpenGroup(for: thread.uniqueId!) {
openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)")
} else if let openGroup = storage.getOpenGroup(for: thread.uniqueId!) {
openGroups.insert(openGroup.server)
}
default: break
}

View File

@ -47,17 +47,10 @@ extension MessageSender {
let (promise, seal) = Promise<Void>.pending()
AttachmentUploadJob.upload(stream, using: { data in return OpenGroupAPIV2.upload(data, to: v2OpenGroup.room, on: v2OpenGroup.server) }, encrypt: false, onSuccess: { seal.fulfill(()) }, onFailure: { seal.reject($0) })
return promise
} else if Features.useV2FileServer && storage.getOpenGroup(for: thread.uniqueId!) == nil {
} else {
let (promise, seal) = Promise<Void>.pending()
AttachmentUploadJob.upload(stream, using: FileServerAPIV2.upload, encrypt: true, onSuccess: { seal.fulfill(()) }, onFailure: { seal.reject($0) })
return promise
} else { // Legacy
let openGroup = storage.getOpenGroup(for: thread.uniqueId!)
let server = openGroup?.server ?? FileServerAPI.server
let maxRetryCount: UInt = (openGroup != nil) ? 24 : 8
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .userInitiated)) {
FileServerAPI.uploadAttachment(stream, with: stream.uniqueId!, to: server)
}
}
}
return when(resolved: attachmentUploadPromises).then(on: DispatchQueue.global(qos: .userInitiated)) { results -> Promise<Void> in

View File

@ -396,18 +396,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
avatarUrl:(nullable NSString *)avatarURL
success:(void (^)(void))successBlock
failure:(ProfileManagerFailureBlock)failureBlock {
OWSAssertDebug(successBlock);
OWSAssertDebug(failureBlock);
NSDictionary *publicChats = [LKStorage.shared getAllUserOpenGroups];
NSSet *servers = [NSSet setWithArray:[publicChats.allValues map:^NSString *(SNOpenGroup *publicChat) { return publicChat.server; }]];
for (NSString *server in servers) {
[[SNOpenGroupAPI setDisplayName:localProfileName on:server] retainUntilComplete];
[[SNOpenGroupAPI setProfilePictureURL:avatarURL usingProfileKey:self.localProfileKey.keyData on:server] retainUntilComplete];
}
successBlock();
}
@ -805,14 +793,9 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
NSString *profilePictureURL = userProfile.avatarUrlPath;
AnyPromise *promise;
if ([profilePictureURL containsString:SNFileServerAPIV2.server] || [profilePictureURL containsString:SNFileServerAPIV2.oldServer]) {
NSString *file = [profilePictureURL lastPathComponent];
BOOL useOldServer = [profilePictureURL containsString:SNFileServerAPIV2.oldServer];
promise = [SNFileServerAPIV2 download:file useOldServer:useOldServer];
} else {
promise = [SNFileServerAPI downloadAttachmentFrom:profilePictureURL];
}
NSString *file = [profilePictureURL lastPathComponent];
BOOL useOldServer = [profilePictureURL containsString:SNFileServerAPIV2.oldServer];
AnyPromise *promise = [SNFileServerAPIV2 download:file useOldServer:useOldServer];
[promise.then(^(NSData *data) {
@synchronized(self.currentAvatarDownloads)