diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index aaf63c541..d49308ba0 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -251,7 +251,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 */; }; @@ -380,7 +379,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 +676,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 */; }; @@ -1321,7 +1312,6 @@ C33FDA87255A57FC00E217F9 /* TypingIndicators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicators.swift; sourceTree = ""; }; C33FDA88255A57FD00E217F9 /* YapDatabaseTransaction+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "YapDatabaseTransaction+OWS.h"; sourceTree = ""; }; C33FDA8B255A57FD00E217F9 /* AppVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppVersion.m; sourceTree = ""; }; - C33FDA8C255A57FD00E217F9 /* OpenGroupPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupPoller.swift; sourceTree = ""; }; C33FDA8E255A57FD00E217F9 /* OWSFileSystem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSFileSystem.m; sourceTree = ""; }; C33FDA90255A57FD00E217F9 /* TSYapDatabaseObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSYapDatabaseObject.m; sourceTree = ""; }; C33FDA96255A57FE00E217F9 /* OWSDispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDispatch.h; sourceTree = ""; }; @@ -1680,18 +1670,10 @@ C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+messagePadding.h"; sourceTree = ""; }; C3A71D662558A0170043A11F /* DiffieHellman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffieHellman.swift; sourceTree = ""; }; C3A71F882558BA9F0043A11F /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = ""; }; - C3A721342558BDF90043A11F /* OpenGroupMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupMessage.swift; sourceTree = ""; }; - C3A721352558BDF90043A11F /* OpenGroupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupAPI.swift; sourceTree = ""; }; - C3A721362558BDFA0043A11F /* OpenGroupInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupInfo.swift; sourceTree = ""; }; - C3A721372558BDFA0043A11F /* OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroup.swift; sourceTree = ""; }; - C3A7218F2558C0CD0043A11F /* FileServerAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileServerAPI.swift; sourceTree = ""; }; C3A721992558C1660043A11F /* AnyPromise+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyPromise+Conversion.swift"; sourceTree = ""; }; - C3A722292558C1E40043A11F /* DotNetAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DotNetAPI.swift; sourceTree = ""; }; C3A7225D2558C38D0043A11F /* Promise+Retaining.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Retaining.swift"; sourceTree = ""; }; - C3A7229B2558E4310043A11F /* OpenGroupMessage+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupMessage+Conversion.swift"; sourceTree = ""; }; C3A76A8C25DB83F90074CB90 /* PermissionMissingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionMissingModal.swift; sourceTree = ""; }; 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 = ""; }; - C3AAFFCB25AE92150089E6DD /* OpenGroupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManager.swift; sourceTree = ""; }; C3AAFFDE25AE96FF0089E6DD /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = ""; }; C3AAFFF125AE99710089E6DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C3AECBEA24EF5244005743DE /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = ""; }; @@ -2460,31 +2442,6 @@ path = Meta; sourceTree = ""; }; - C3227FF4260AAD58006EA627 /* V2 */ = { - isa = PBXGroup; - children = ( - C3DB6694260AC923001EFC55 /* OpenGroupV2.swift */, - B88FA7B726045D100049422F /* OpenGroupAPIV2.swift */, - C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */, - C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */, - C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */, - ); - path = V2; - sourceTree = ""; - }; - 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 = ""; - }; C328252E25CA54F70062D0A7 /* Context Menu */ = { isa = PBXGroup; children = ( @@ -2532,7 +2489,6 @@ isa = PBXGroup; children = ( C33FDB34255A580B00E217F9 /* ClosedGroupPoller.swift */, - C33FDA8C255A57FD00E217F9 /* OpenGroupPoller.swift */, C3DB66C2260ACCE6001EFC55 /* OpenGroupPollerV2.swift */, C33FDB3A255A580B00E217F9 /* Poller.swift */, ); @@ -3138,8 +3094,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 = ""; @@ -3147,7 +3106,6 @@ C3A7215C2558C0AC0043A11F /* File Server */ = { isa = PBXGroup; children = ( - C3A7218F2558C0CD0043A11F /* FileServerAPI.swift */, B87EF17026367CF800124B3C /* FileServerAPIV2.swift */, ); path = "File Server"; @@ -3159,7 +3117,6 @@ C33FDB01255A580700E217F9 /* AppReadiness.h */, C33FDB75255A581000E217F9 /* AppReadiness.m */, C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */, - C3A722292558C1E40043A11F /* DotNetAPI.swift */, C37F53E8255BA9BB002AEA92 /* Environment.h */, C37F5402255BA9ED002AEA92 /* Environment.m */, C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */, @@ -4672,8 +4629,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 */, @@ -4698,10 +4653,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 */, @@ -4722,12 +4675,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 */, @@ -4746,7 +4697,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 */, @@ -4761,7 +4711,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 */, @@ -4795,7 +4744,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 */, diff --git a/SessionMessagingKit/Database/Storage+Messaging.swift b/SessionMessagingKit/Database/Storage+Messaging.swift index ca4149fa0..0314c037c 100644 --- a/SessionMessagingKit/Database/Storage+Messaging.swift +++ b/SessionMessagingKit/Database/Storage+Messaging.swift @@ -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 } diff --git a/SessionMessagingKit/Database/Storage+OpenGroups.swift b/SessionMessagingKit/Database/Storage+OpenGroups.swift index 93a563a45..5f9539370 100644 --- a/SessionMessagingKit/Database/Storage+OpenGroups.swift +++ b/SessionMessagingKit/Database/Storage+OpenGroups.swift @@ -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" @@ -205,10 +185,6 @@ extension Storage { (transaction as! YapDatabaseReadWriteTransaction).setObject(messageID, forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection) } - 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 @@ -220,148 +196,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) - } } diff --git a/SessionMessagingKit/File Server/FileServerAPI.swift b/SessionMessagingKit/File Server/FileServerAPI.swift deleted file mode 100644 index 3852ffeaa..000000000 --- a/SessionMessagingKit/File Server/FileServerAPI.swift +++ /dev/null @@ -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 { - 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 { - 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() - } - } -} diff --git a/SessionMessagingKit/File Server/FileServerAPIV2.swift b/SessionMessagingKit/File Server/FileServerAPIV2.swift index 127d9c632..5bf08162d 100644 --- a/SessionMessagingKit/File Server/FileServerAPIV2.swift +++ b/SessionMessagingKit/File Server/FileServerAPIV2.swift @@ -4,20 +4,32 @@ import SessionSnodeKit @objc(SNFileServerAPIV2) public final class FileServerAPIV2 : NSObject { + // MARK: Settings @objc public static let server = "http://88.99.175.227" public static let serverPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" + 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() { } // MARK: Error 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." } } } diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index d9b96b5b9..1f390d411 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -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) { + } else { guard let fileAsString = pointer.downloadURL.split(separator: "/").last, let file = UInt64(fileAsString) else { return handleFailure(Error.invalidURL) } @@ -109,12 +102,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) - } } } diff --git a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift index 3a70680cb..5e81f0fa4 100644 --- a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift @@ -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 diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 8587d5619..e1259b8bb 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -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.") } diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2+ObjC.swift b/SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift similarity index 100% rename from SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2+ObjC.swift rename to SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift b/SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift similarity index 100% rename from SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift rename to SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupManagerV2.swift b/SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift similarity index 100% rename from SessionMessagingKit/Open Groups/V2/OpenGroupManagerV2.swift rename to SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupMessageV2.swift b/SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift similarity index 100% rename from SessionMessagingKit/Open Groups/V2/OpenGroupMessageV2.swift rename to SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupV2.swift b/SessionMessagingKit/Open Groups/OpenGroupV2.swift similarity index 100% rename from SessionMessagingKit/Open Groups/V2/OpenGroupV2.swift rename to SessionMessagingKit/Open Groups/OpenGroupV2.swift diff --git a/SessionMessagingKit/Open Groups/V1/OpenGroup.swift b/SessionMessagingKit/Open Groups/V1/OpenGroup.swift deleted file mode 100644 index 2c41221e6..000000000 --- a/SessionMessagingKit/Open Groups/V1/OpenGroup.swift +++ /dev/null @@ -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))" } -} diff --git a/SessionMessagingKit/Open Groups/V1/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/V1/OpenGroupAPI.swift deleted file mode 100644 index 59479d1de..000000000 --- a/SessionMessagingKit/Open Groups/V1/OpenGroupAPI.swift +++ /dev/null @@ -1,519 +0,0 @@ -import AFNetworking -import PromiseKit -import SessionSnodeKit -import SessionUtilitiesKit - -@objc(SNOpenGroupAPI) -public final class OpenGroupAPI : DotNetAPI { - private static var moderators: [String:[UInt64:Set]] = [:] // Server URL to (channel ID to set of moderator IDs) - - public static var displayNameUpdatees: [String:Set] = [:] - - // 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 { - 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 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 - } - let existingMessageID = storage.getIDForMessage(withServerID: result.serverID!) - guard existingMessageID == nil else { - SNLog("Ignoring duplicate open group message.") - 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 { - 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.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 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 { - 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 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 { - 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 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 { - 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 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.. AnyPromise { - return AnyPromise.from(setDisplayName(to: newDisplayName, on: server)) - } - - public static func setDisplayName(to newDisplayName: String?, on server: String) -> Promise { - 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 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 { - 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 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 { - 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 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 { - 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 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 { - 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 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 { - 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> { - return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise> 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 { - return recover(on: DispatchQueue.global(qos: .userInitiated)) { error -> Promise 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 - } - } -} diff --git a/SessionMessagingKit/Open Groups/V1/OpenGroupInfo.swift b/SessionMessagingKit/Open Groups/V1/OpenGroupInfo.swift deleted file mode 100644 index d842e1bf9..000000000 --- a/SessionMessagingKit/Open Groups/V1/OpenGroupInfo.swift +++ /dev/null @@ -1,6 +0,0 @@ - -public struct OpenGroupInfo { - public let displayName: String - public let profilePictureURL: String? - public let memberCount: Int -} diff --git a/SessionMessagingKit/Open Groups/V1/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/V1/OpenGroupManager.swift deleted file mode 100644 index a4d98591b..000000000 --- a/SessionMessagingKit/Open Groups/V1/OpenGroupManager.swift +++ /dev/null @@ -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 { - 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 = [] - var messageTimestamps: Set = [] - 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) - } -} diff --git a/SessionMessagingKit/Open Groups/V1/OpenGroupMessage+Conversion.swift b/SessionMessagingKit/Open Groups/V1/OpenGroupMessage+Conversion.swift deleted file mode 100644 index 490f48958..000000000 --- a/SessionMessagingKit/Open Groups/V1/OpenGroupMessage+Conversion.swift +++ /dev/null @@ -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 - } -} diff --git a/SessionMessagingKit/Open Groups/V1/OpenGroupMessage.swift b/SessionMessagingKit/Open Groups/V1/OpenGroupMessage.swift deleted file mode 100644 index ab8feb3d2..000000000 --- a/SessionMessagingKit/Open Groups/V1/OpenGroupMessage.swift +++ /dev/null @@ -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) - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Mentions/MentionsManager.swift b/SessionMessagingKit/Sending & Receiving/Mentions/MentionsManager.swift index 820cdf93a..e1117aa21 100644 --- a/SessionMessagingKit/Sending & Receiving/Mentions/MentionsManager.swift +++ b/SessionMessagingKit/Sending & Receiving/Mentions/MentionsManager.swift @@ -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 } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 78dad3a38..7c65dea94 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -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.. AnyPromise { - AnyPromise.from(pollForNewMessages()) - } - - @discardableResult - public func pollForNewMessages() -> Promise { - guard isMainAppAndActive else { stop(); return Promise.value(()) } - return pollForNewMessages(isBackgroundPoll: false) - } - - @discardableResult - public func pollForNewMessages(isBackgroundPoll: Bool) -> Promise { - guard !self.isPolling else { return Promise.value(()) } - self.isPolling = true - let openGroup = self.openGroup - let (promise, seal) = Promise.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.. [String:OpenGroupV2] func getV2OpenGroup(for threadID: String) -> OpenGroupV2? - func getThreadID(for openGroupID: String) -> String? func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set, using transaction: Any) // MARK: - Open Group Public Keys @@ -72,8 +71,6 @@ public protocol SessionMessagingKitStorageProtocol { func setUserCount(to newValue: UInt64, forV2OpenGroupWithID openGroupID: String, using transaction: Any) func getIDForMessage(withServerID serverID: UInt64) -> String? func setIDForMessage(withServerID serverID: UInt64, to messageID: 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 @@ -89,23 +86,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) } diff --git a/SessionMessagingKit/Utilities/DotNetAPI.swift b/SessionMessagingKit/Utilities/DotNetAPI.swift deleted file mode 100644 index 2ef017a8e..000000000 --- a/SessionMessagingKit/Utilities/DotNetAPI.swift +++ /dev/null @@ -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 { - 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.. Promise { - 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 { - 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 { - 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 { - let isEncryptionRequired = (server == FileServerAPI.server) - return Promise() { 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) - } - } - } - } -} diff --git a/SessionUtilitiesKit/General/Features.swift b/SessionUtilitiesKit/General/Features.swift index e4d62c53d..d23dabef6 100644 --- a/SessionUtilitiesKit/General/Features.swift +++ b/SessionUtilitiesKit/General/Features.swift @@ -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 = false } diff --git a/SignalUtilitiesKit/Configuration.swift b/SignalUtilitiesKit/Configuration.swift index 0ae9bde47..11234855e 100644 --- a/SignalUtilitiesKit/Configuration.swift +++ b/SignalUtilitiesKit/Configuration.swift @@ -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)) } } diff --git a/SignalUtilitiesKit/Messaging/ConfigurationMessage+Convenience.swift b/SignalUtilitiesKit/Messaging/ConfigurationMessage+Convenience.swift index 101e68fbc..1162e7f24 100644 --- a/SignalUtilitiesKit/Messaging/ConfigurationMessage+Convenience.swift +++ b/SignalUtilitiesKit/Messaging/ConfigurationMessage+Convenience.swift @@ -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 } diff --git a/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift b/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift index 4822e8547..177cdb65d 100644 --- a/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift +++ b/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift @@ -47,17 +47,10 @@ extension MessageSender { let (promise, seal) = Promise.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.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 in diff --git a/SignalUtilitiesKit/To Do/OWSProfileManager.m b/SignalUtilitiesKit/To Do/OWSProfileManager.m index 5a6817681..57c23a637 100644 --- a/SignalUtilitiesKit/To Do/OWSProfileManager.m +++ b/SignalUtilitiesKit/To Do/OWSProfileManager.m @@ -362,12 +362,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); NSData *encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey]; OWSAssertDebug(encryptedAvatarData.length > 0); - AnyPromise *promise; - if (SNFeatures.useV2FileServer) { - promise = [SNFileServerAPIV2 upload:encryptedAvatarData]; - } else { - promise = [SNFileServerAPI uploadProfilePicture:encryptedAvatarData]; - } + AnyPromise *promise = [SNFileServerAPIV2 upload:encryptedAvatarData]; [promise.thenOn(dispatch_get_main_queue(), ^(NSString *downloadURL) { [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ @@ -399,18 +394,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(); } @@ -808,13 +791,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); NSString *profilePictureURL = userProfile.avatarUrlPath; - AnyPromise *promise; - if ([profilePictureURL containsString:SNFileServerAPIV2.server]) { - uint64_t *file = (uint64_t)[[profilePictureURL lastPathComponent] intValue]; - promise = [SNFileServerAPIV2 download:file]; - } else { - promise = [SNFileServerAPI downloadAttachmentFrom:profilePictureURL]; - } + uint64_t *file = (uint64_t)[[profilePictureURL lastPathComponent] intValue]; + AnyPromise *promise = [SNFileServerAPIV2 download:file]; [promise.then(^(NSData *data) { @synchronized(self.currentAvatarDownloads)