diff --git a/Session/AppDelegate+OpenGroupAPI.swift b/Session/AppDelegate+OpenGroupAPI.swift new file mode 100644 index 000000000..669c5880e --- /dev/null +++ b/Session/AppDelegate+OpenGroupAPI.swift @@ -0,0 +1,37 @@ + +extension AppDelegate : OpenGroupAPIDelegate { + + public func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: OpenGroupInfo) { + let storage = OWSPrimaryStorage.shared() + let publicChatID = "\(server).\(channel)" + Storage.writeSync { transaction in + // Update user count + storage.setUserCount(info.memberCount, forPublicChatWithID: publicChatID, in: transaction) + let groupThread = TSGroupThread.getOrCreateThread(withGroupId: publicChatID.data(using: .utf8)!, groupType: .openGroup, transaction: transaction) + // Update display name if needed + let groupModel = groupThread.groupModel + if groupModel.groupName != info.displayName { + let newGroupModel = TSGroupModel(title: info.displayName, memberIds: groupModel.groupMemberIds, image: groupModel.groupImage, groupId: groupModel.groupId, groupType: groupModel.groupType, adminIds: groupModel.groupAdminIds) + groupThread.groupModel = newGroupModel + groupThread.save(with: transaction) + } + // Download and update profile picture if needed + let oldProfilePictureURL = storage.getProfilePictureURL(forPublicChatWithID: publicChatID, in: transaction) + if oldProfilePictureURL != info.profilePictureURL || groupModel.groupImage == nil { + storage.setProfilePictureURL(info.profilePictureURL, forPublicChatWithID: publicChatID, in: transaction) + if let profilePictureURL = info.profilePictureURL { + var sanitizedServerURL = server + var sanitizedProfilePictureURL = profilePictureURL + while sanitizedServerURL.hasSuffix("/") { sanitizedServerURL.removeLast(1) } + while sanitizedProfilePictureURL.hasPrefix("/") { sanitizedProfilePictureURL.removeFirst(1) } + let url = "\(sanitizedServerURL)/\(sanitizedProfilePictureURL)" + FileServerAPI.downloadAttachment(from: url).map2 { data in + let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(data.count), sourceFilename: nil, caption: nil, albumMessageId: nil) + try attachmentStream.write(data) + groupThread.updateAvatar(with: attachmentStream) + } + } + } + } + } +} diff --git a/Session/Configuration.swift b/Session/Configuration.swift index a30b8c684..4982d88a1 100644 --- a/Session/Configuration.swift +++ b/Session/Configuration.swift @@ -1,10 +1,24 @@ +import SessionMessagingKit import SessionProtocolKit import SessionSnodeKit @objc(SNConfiguration) final class Configuration : NSObject { + private static let pnServerURL = "https://live.apns.getsession.org" + private static let pnServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" + @objc static func performMainSetup() { + SNMessagingKit.configure( + storage: Storage.shared, + signalStorage: OWSPrimaryStorage.shared(), + identityKeyStore: OWSIdentityManager.shared(), + sessionRestorationImplementation: SessionRestorationImplementation(), + certificateValidator: SMKCertificateDefaultValidator(trustRoot: OWSUDManagerImpl.trustRoot()), + openGroupAPIDelegate: UIApplication.shared.delegate as! AppDelegate, + pnServerURL: pnServerURL, + pnServerPublicKey: pnServerURL + ) SessionProtocolKit.configure(storage: Storage.shared, sharedSenderKeysDelegate: UIApplication.shared.delegate as! AppDelegate) SessionSnodeKit.configure(storage: Storage.shared) } diff --git a/Session/Database/Storage+SessionMessagingKit.swift b/Session/Database/Storage+SessionMessagingKit.swift new file mode 100644 index 000000000..66156eee6 --- /dev/null +++ b/Session/Database/Storage+SessionMessagingKit.swift @@ -0,0 +1,158 @@ +import Foundation +import PromiseKit + +extension Storage : SessionMessagingKitStorageProtocol { + + // MARK: Signal Protocol + public func getOrGenerateRegistrationID(using transaction: Any) -> UInt32 { + SSKEnvironment.shared.tsAccountManager.getOrGenerateRegistrationId(transaction as! YapDatabaseReadWriteTransaction) + } + + public func getSenderCertificate(for publicKey: String) -> SMKSenderCertificate { + let (promise, seal) = Promise.pending() + SSKEnvironment.shared.udManager.ensureSenderCertificate { senderCertificate in + seal.fulfill(senderCertificate) + } failure: { error in + // Should never fail + } + return try! promise.wait() + } + + // MARK: Shared Sender Keys + private static let closedGroupPrivateKeyCollection = "LokiClosedGroupPrivateKeyCollection" + + public func getClosedGroupPrivateKey(for publicKey: String) -> String? { + var result: String? + Storage.read { transaction in + result = transaction.object(forKey: publicKey, inCollection: Storage.closedGroupPrivateKeyCollection) as? String + } + return result + } + + internal static func setClosedGroupPrivateKey(_ privateKey: String, for publicKey: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(privateKey, forKey: publicKey, inCollection: Storage.closedGroupPrivateKeyCollection) + } + + internal static func removeClosedGroupPrivateKey(for publicKey: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: publicKey, inCollection: Storage.closedGroupPrivateKeyCollection) + } + + func getUserClosedGroupPublicKeys() -> Set { + var result: Set = [] + Storage.read { transaction in + result = Set(transaction.allKeys(inCollection: Storage.closedGroupPrivateKeyCollection)) + } + return result + } + + public func isClosedGroup(_ publicKey: String) -> Bool { + getUserClosedGroupPublicKeys().contains(publicKey) + } + + // MARK: Jobs + public func persist(_ job: Job, using transaction: Any) { fatalError("Not implemented.") } + public func markJobAsSucceeded(_ job: Job, using transaction: Any) { fatalError("Not implemented.") } + public func markJobAsFailed(_ job: Job, using transaction: Any) { fatalError("Not implemented.") } + + // MARK: Authorization + 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) + } + + // MARK: Open Group Public Keys + private static let openGroupPublicKeyCollection = "LokiOpenGroupPublicKeyCollection" + + public func getOpenGroupPublicKey(for server: String) -> String? { + var result: String? = nil + Storage.read { transaction in + result = transaction.object(forKey: server, inCollection: Storage.openGroupPublicKeyCollection) as? String + } + return result + } + + public func setOpenGroupPublicKey(for server: String, to newValue: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: server, inCollection: Storage.openGroupPublicKeyCollection) + } + + // MARK: Last Message Server ID + private static let lastMessageServerIDCollection = "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.lastMessageServerIDCollection) 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.lastMessageServerIDCollection) + } + + public func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.lastMessageServerIDCollection) + } + + // MARK: Last Deletion Server ID + private static let lastDeletionServerIDCollection = "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.lastDeletionServerIDCollection) 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.lastDeletionServerIDCollection) + } + + public func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.lastDeletionServerIDCollection) + } + + // MARK: Open Group Metadata + private static let openGroupUserCountCollection = "LokiPublicChatUserCountCollection" + private static let openGroupMessageIDCollection = "LKMessageIDCollection" + + public func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: openGroupID, inCollection: Storage.openGroupUserCountCollection) + } + + public func getIDForMessage(withServerID serverID: UInt64) -> UInt64? { + var result: UInt64? = nil + Storage.read { transaction in + result = transaction.object(forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection) as? UInt64 + } + return result + } + + public func setOpenGroupDisplayName(to displayName: String, for publicKey: String, on channel: UInt64, server: String, using transaction: Any) { + let collection = "\(server).\(channel)" // FIXME: This should be a proper collection + (transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection) + } + + public func setLastProfilePictureUploadDate(_ date: Date) { + UserDefaults.standard[.lastProfilePictureUpload] = date + } +} diff --git a/Session/Database/Storage+SessionSnodeKit.swift b/Session/Database/Storage+SessionSnodeKit.swift index 356cd72c9..032fcd439 100644 --- a/Session/Database/Storage+SessionSnodeKit.swift +++ b/Session/Database/Storage+SessionSnodeKit.swift @@ -2,7 +2,7 @@ extension Storage : SessionSnodeKitStorageProtocol { // MARK: Onion Request Paths - internal static let onionRequestPathCollection = "LokiOnionRequestPathCollection" + private static let onionRequestPathCollection = "LokiOnionRequestPathCollection" public func getOnionRequestPaths() -> [OnionRequestAPI.Path] { let collection = Storage.onionRequestPathCollection diff --git a/Session/Database/Storage+Shared.swift b/Session/Database/Storage+Shared.swift index 10741e332..7910e7532 100644 --- a/Session/Database/Storage+Shared.swift +++ b/Session/Database/Storage+Shared.swift @@ -7,7 +7,17 @@ extension Storage { Storage.writeSync { work($0) } } + public func withAsync(_ work: @escaping (Any) -> Void, completion: @escaping () -> Void) { + Storage.write(with: { work($0) }, completion: completion) + } + public func getUserPublicKey() -> String? { return OWSIdentityManager.shared().identityKeyPair()?.publicKey.toHexString() } + + public func getUserKeyPair() -> ECKeyPair? { + return OWSIdentityManager.shared().identityKeyPair() + } + + public func getUserDisplayName() -> String? { fatalError() } } diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 649cc8654..525b9f9bb 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -13,7 +13,7 @@ public struct Configuration { internal static var shared: Configuration! } -public enum SessionMessagingKitX { // Just to make the external API nice +public enum SNMessagingKit { // Just to make the external API nice public static func configure( storage: SessionMessagingKitStorageProtocol, diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 6690066e2..82ee5c6d7 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -122,7 +122,7 @@ public final class OpenGroupAPI : DotNetAPI { SNLog("Ignoring open group message with invalid signature.") return nil } - let existingMessageID = storage.getIDForMessage(withServerID: UInt(result.serverID!)) + let existingMessageID = storage.getIDForMessage(withServerID: result.serverID!) guard existingMessageID == nil else { SNLog("Ignoring duplicate open group message.") return nil diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 5d050fad6..2b3455892 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -2,8 +2,8 @@ import SessionProtocolKit public protocol SessionMessagingKitStorageProtocol { - func with(_ work: (Any) -> Void) - func withAsync(_ work: (Any) -> Void, completion: () -> Void) + func with(_ work: @escaping (Any) -> Void) + func withAsync(_ work: @escaping (Any) -> Void, completion: @escaping () -> Void) func getUserPublicKey() -> String? func getUserKeyPair() -> ECKeyPair? @@ -20,14 +20,14 @@ public protocol SessionMessagingKitStorageProtocol { func removeAuthToken(for server: String, using transaction: Any) func getOpenGroupPublicKey(for server: String) -> String? func setOpenGroupPublicKey(for server: String, to newValue: String, using transaction: Any) - func getLastMessageServerID(for group: UInt64, on server: String) -> UInt? + 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 setUserCount(to newValue: Int, forOpenGroupWithID: String, using transaction: Any) - func getIDForMessage(withServerID serverID: UInt) -> UInt? + func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any) + func getIDForMessage(withServerID serverID: UInt64) -> UInt64? func setOpenGroupDisplayName(to displayName: String, for publicKey: String, on channel: UInt64, server: String, using transaction: Any) func setLastProfilePictureUploadDate(_ date: Date) // Stored in user defaults so no transaction is needed } diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 0a7ef7111..640ba3bc0 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -801,6 +801,8 @@ C3548F0624456447009433A8 /* PNModeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0524456447009433A8 /* PNModeVC.swift */; }; C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */; }; C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C354E75923FE2A7600CE22E3 /* BaseVC.swift */; }; + C3550A03255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3550A02255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift */; }; + C3550A1D255DD73500194B6A /* Storage+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3550A1C255DD73500194B6A /* Storage+SessionMessagingKit.swift */; }; C35E8AA82485C85800ACB629 /* GeoLite2-Country-Locations-English.csv in Resources */ = {isa = PBXBuildFile; fileRef = C35E8AA52485C85400ACB629 /* GeoLite2-Country-Locations-English.csv */; }; C35E8AA92485C85800ACB629 /* GeoLite2-Country-Blocks-IPv4.csv in Resources */ = {isa = PBXBuildFile; fileRef = C35E8AA62485C85600ACB629 /* GeoLite2-Country-Blocks-IPv4.csv */; }; C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35E8AAD2485E51D00ACB629 /* IP2Country.swift */; }; @@ -2165,6 +2167,8 @@ C3548F0524456447009433A8 /* PNModeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeVC.swift; sourceTree = ""; }; C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Wrapping.swift"; sourceTree = ""; }; C354E75923FE2A7600CE22E3 /* BaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseVC.swift; sourceTree = ""; }; + C3550A02255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+OpenGroupAPI.swift"; sourceTree = ""; }; + C3550A1C255DD73500194B6A /* Storage+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+SessionMessagingKit.swift"; sourceTree = ""; }; C35E8AA22485C72300ACB629 /* SwiftCSV.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftCSV.framework; path = ThirdParty/Carthage/Build/iOS/SwiftCSV.framework; sourceTree = ""; }; C35E8AA52485C85400ACB629 /* GeoLite2-Country-Locations-English.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "GeoLite2-Country-Locations-English.csv"; sourceTree = ""; }; C35E8AA62485C85600ACB629 /* GeoLite2-Country-Blocks-IPv4.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "GeoLite2-Country-Blocks-IPv4.csv"; sourceTree = ""; }; @@ -3272,6 +3276,7 @@ C31F812425258F9C00DD9FD9 /* Database */ = { isa = PBXGroup; children = ( + C3550A1C255DD73500194B6A /* Storage+SessionMessagingKit.swift */, C3F0A619255C9902007BE2A3 /* Storage+SessionProtocolKit.swift */, C3F0A607255C98A6007BE2A3 /* Storage+SessionSnodeKit.swift */, C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */, @@ -4477,6 +4482,7 @@ isa = PBXGroup; children = ( C3F0A58F255C8E3D007BE2A3 /* Meta */, + C3550A02255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift */, C3F0A62B255C9937007BE2A3 /* AppDelegate+SharedSenderKeys.swift */, C3F0A5EB255C970D007BE2A3 /* Configuration.swift */, B8CCF63B239757C10091D419 /* Components */, @@ -6159,6 +6165,7 @@ 34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */, B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */, B80C6B5B2384C7F900FDBC8B /* DeviceNameModalDelegate.swift in Sources */, + C3550A03255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift in Sources */, 340FC8B8204DAC8D007AEB0F /* AddToGroupViewController.m in Sources */, B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */, B879D449247E1BE300DB3608 /* PathVC.swift in Sources */, @@ -6268,6 +6275,7 @@ 340FC8AB204DAC8D007AEB0F /* DomainFrontingCountryViewController.m in Sources */, 4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */, 3496744D2076768700080B5F /* OWSMessageBubbleView.m in Sources */, + C3550A1D255DD73500194B6A /* Storage+SessionMessagingKit.swift in Sources */, C331FFF42558FF0300070591 /* PNOptionView.swift in Sources */, 34B3F8751E8DF1700035BE1A /* CallViewController.swift in Sources */, 4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */,