diff --git a/Pods b/Pods
index 8f3d6d467..8b8d1d35f 160000
--- a/Pods
+++ b/Pods
@@ -1 +1 @@
-Subproject commit 8f3d6d46718795227074d6545776206cf18c0244
+Subproject commit 8b8d1d35f47af7071caaaee0aca8c1f5806bbe8e
diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj
index 02faca95d..23bae1487 100644
--- a/Signal.xcodeproj/project.pbxproj
+++ b/Signal.xcodeproj/project.pbxproj
@@ -519,7 +519,6 @@
B80C6B572384A56D00FDBC8B /* DeviceLinksVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80C6B562384A56D00FDBC8B /* DeviceLinksVC.swift */; };
B80C6B592384C4E700FDBC8B /* DeviceNameModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80C6B582384C4E700FDBC8B /* DeviceNameModal.swift */; };
B80C6B5B2384C7F900FDBC8B /* DeviceNameModalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80C6B5A2384C7F900FDBC8B /* DeviceNameModalDelegate.swift */; };
- B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */; };
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40872399EB0E00A248E7 /* LandingVC.swift */; };
B82B408A2399EC0600A248E7 /* FakeChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40892399EC0600A248E7 /* FakeChatView.swift */; };
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408B239A068800A248E7 /* RegisterVC.swift */; };
@@ -1340,7 +1339,6 @@
B80C6B562384A56D00FDBC8B /* DeviceLinksVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinksVC.swift; sourceTree = ""; };
B80C6B582384C4E700FDBC8B /* DeviceNameModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceNameModal.swift; sourceTree = ""; };
B80C6B5A2384C7F900FDBC8B /* DeviceNameModalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceNameModalDelegate.swift; sourceTree = ""; };
- B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiRSSFeedPoller.swift; sourceTree = ""; };
B82B40872399EB0E00A248E7 /* LandingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingVC.swift; sourceTree = ""; };
B82B40892399EC0600A248E7 /* FakeChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeChatView.swift; sourceTree = ""; };
B82B408B239A068800A248E7 /* RegisterVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterVC.swift; sourceTree = ""; };
@@ -2616,7 +2614,6 @@
children = (
B8CCF63B239757C10091D419 /* Components */,
C32B405424A961E1001117B5 /* Dependencies */,
- B8BFFF392355426100102A27 /* Shelved */,
B8CCF63C239757DB0091D419 /* Utilities */,
B8CCF63D2397580E0091D419 /* View Controllers */,
);
@@ -2664,14 +2661,6 @@
path = Loki;
sourceTree = "";
};
- B8BFFF392355426100102A27 /* Shelved */ = {
- isa = PBXGroup;
- children = (
- B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */,
- );
- path = Shelved;
- sourceTree = "";
- };
B8C9689223FA1B05005F64E0 /* Redesign */ = {
isa = PBXGroup;
children = (
@@ -3895,7 +3884,6 @@
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */,
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */,
3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */,
- B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */,
C353F8F7244808E90011121A /* PNModeSheet.swift in Sources */,
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */,
diff --git a/Signal/src/Loki/Shelved/LokiRSSFeedPoller.swift b/Signal/src/Loki/Shelved/LokiRSSFeedPoller.swift
deleted file mode 100644
index a0f15ea02..000000000
--- a/Signal/src/Loki/Shelved/LokiRSSFeedPoller.swift
+++ /dev/null
@@ -1,73 +0,0 @@
-import FeedKit
-
-@objc(LKRSSFeedPoller)
-public final class LokiRSSFeedPoller : NSObject {
- private let feed: LokiRSSFeed
- private var timer: Timer? = nil
- private var hasStarted = false
-
- private let interval: TimeInterval = 8 * 60
-
- @objc(initForFeed:)
- public init(for feed: LokiRSSFeed) {
- self.feed = feed
- super.init()
- }
-
- @objc public func startIfNeeded() {
- if hasStarted { return }
- timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in self?.poll() }
- poll() // Perform initial update
- hasStarted = true
- }
-
- @objc public func stop() {
- timer?.invalidate()
- hasStarted = false
- }
-
- private func poll() {
- let feed = self.feed
- let url = feed.server
- let _ = LokiRSSFeedProxy.fetchContent(for: url).done { xml in
- guard let data = xml.data(using: String.Encoding.utf8) else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") }
- FeedParser(data: data).parseAsync { wrapper in
- guard case .rss(let x) = wrapper, let items = x.items else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") }
- items.reversed().forEach { item in
- guard let title = item.title, let description = item.description, let date = item.pubDate else { return }
- let timestamp = UInt64(date.timeIntervalSince1970 * 1000)
- let urlRegex = try! NSRegularExpression(pattern: "]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>")
- var bodyAsHTML = "\(title)
\(description)".replacingOccurrences(of: "
", with: "
")
- while true {
- guard let match = urlRegex.firstMatch(in: bodyAsHTML, options: [], range: NSRange(location: 0, length: bodyAsHTML.utf16.count)) else { break }
- let matchRange = match.range(at: 0)
- let urlRange = match.range(at: 1)
- let descriptionRange = match.range(at: 2)
- let url = (bodyAsHTML as NSString).substring(with: urlRange)
- let description = (bodyAsHTML as NSString).substring(with: descriptionRange)
- bodyAsHTML = (bodyAsHTML as NSString).replacingCharacters(in: matchRange, with: "\(description) (\(url))") as String
- }
- guard let bodyAsData = bodyAsHTML.data(using: String.Encoding.unicode) else { return }
- let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ]
- guard let body = try? NSAttributedString(data: bodyAsData, options: options, documentAttributes: nil).string else { return }
- let id = LKGroupUtilities.getEncodedRSSFeedIDAsData(feed.id)
- let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
- groupContext.setName(feed.displayName)
- let dataMessage = SSKProtoDataMessage.builder()
- dataMessage.setTimestamp(timestamp)
- dataMessage.setGroup(try! groupContext.build())
- dataMessage.setBody(body)
- let content = SSKProtoContent.builder()
- content.setDataMessage(try! dataMessage.build())
- let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp)
- envelope.setSource(NSLocalizedString("Loki", comment: ""))
- envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
- envelope.setContent(try! content.build().serializedData())
- try! Storage.writeSync { transaction in
- SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0)
- }
- }
- }
- }
- }
-}
diff --git a/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift b/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift
index 3ffcc74da..d76c4c423 100644
--- a/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift
+++ b/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift
@@ -154,7 +154,7 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
.catch(on: .main) { [weak self] error in
var title = NSLocalizedString("Couldn't Join", comment: "")
var message = ""
- if case LokiHTTPClient.HTTPError.networkError(let statusCode, _, _) = error, (statusCode == 401 || statusCode == 403) {
+ if case HTTP.Error.httpRequestFailed(let statusCode, _) = error, statusCode == 401 || statusCode == 403 {
title = NSLocalizedString("Unauthorized", comment: "")
message = NSLocalizedString("Please ask the open group operator to add you to the group.", comment: "")
}
diff --git a/SignalMessaging/environment/VersionMigrations.m b/SignalMessaging/environment/VersionMigrations.m
index c81e76932..70d136897 100644
--- a/SignalMessaging/environment/VersionMigrations.m
+++ b/SignalMessaging/environment/VersionMigrations.m
@@ -95,11 +95,6 @@ NS_ASSUME_NONNULL_BEGIN
if ([self isVersion:previousVersion atLeast:@"2.0.0" andLessThan:@"2.3.0"] && [self.tsAccountManager isRegistered]) {
[self clearBloomFilterCache];
}
-
- // Loki
- if ([self isVersion:previousVersion lessThan:@"1.2.1"] && [self.tsAccountManager isRegistered]) {
- [self updatePublicChatMapping];
- }
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[OWSDatabaseMigrationRunner alloc] init] runAllOutstandingWithCompletion:completion];
@@ -168,43 +163,6 @@ NS_ASSUME_NONNULL_BEGIN
}
}
-# pragma mark Loki - Upgrading to Public Chat Manager
-
-// Versions less than or equal to 1.2.0 didn't store public chat mappings
-+ (void)updatePublicChatMapping
-{
- [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
- for (LKPublicChat *chat in LKPublicChatAPI.defaultChats) {
- TSGroupThread *thread = [TSGroupThread threadWithGroupId:[LKGroupUtilities getEncodedOpenGroupIDAsData:chat.id] transaction:transaction];
- if (thread != nil) {
- [LKDatabaseUtilities setPublicChat:chat threadID:thread.uniqueId transaction:transaction];
- } else {
- // Update the group type and group ID for private group chat version.
- // If the thread is still using the old group ID, it needs to be updated.
- thread = [TSGroupThread threadWithGroupId:chat.idAsData transaction:transaction];
- if (thread != nil) {
- thread.groupModel.groupType = openGroup;
- [thread.groupModel updateGroupId:[LKGroupUtilities getEncodedOpenGroupIDAsData:chat.id]];
- [thread saveWithTransaction:transaction];
- [LKDatabaseUtilities setPublicChat:chat threadID:thread.uniqueId transaction:transaction];
- }
- }
- }
- // Update RSS feeds here
- LKRSSFeed *lokiNewsFeed = [[LKRSSFeed alloc] initWithId:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki News", @"") isDeletable:true];
- LKRSSFeed *lokiMessengerUpdatesFeed = [[LKRSSFeed alloc] initWithId:@"loki.network.messenger-updates.feed" server:@"https://loki.network/category/messenger-updates/feed/" displayName:NSLocalizedString(@"Session Updates", @"") isDeletable:false];
- NSArray *feeds = @[ lokiNewsFeed, lokiMessengerUpdatesFeed ];
- for (LKRSSFeed *feed in feeds) {
- TSGroupThread *thread = [TSGroupThread threadWithGroupId:[feed.id dataUsingEncoding:NSUTF8StringEncoding] transaction:transaction];
- if (thread != nil) {
- thread.groupModel.groupType = rssFeed;
- [thread.groupModel updateGroupId:[LKGroupUtilities getEncodedRSSFeedIDAsData:feed.id]];
- [thread saveWithTransaction:transaction];
- }
- }
- } error:nil];
-}
-
@end
NS_ASSUME_NONNULL_END
diff --git a/SignalServiceKit/src/Loki/API/Deprecated/LokiFileServerProxy.swift b/SignalServiceKit/src/Loki/API/Deprecated/LokiFileServerProxy.swift
deleted file mode 100644
index 0422ad484..000000000
--- a/SignalServiceKit/src/Loki/API/Deprecated/LokiFileServerProxy.swift
+++ /dev/null
@@ -1,127 +0,0 @@
-import PromiseKit
-import SessionMetadataKit
-
-internal class LokiFileServerProxy : LokiHTTPClient {
- private let server: String
- private let keyPair = Curve25519.generateKeyPair()
-
- private static let fileServerPublicKey: Data = {
- let base64EncodedPublicKey = "BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc"
- let publicKeyWithPrefix = Data(base64Encoded: base64EncodedPublicKey)!
- let hexEncodedPublicKeyWithPrefix = publicKeyWithPrefix.toHexString()
- let hexEncodedPublicKey = hexEncodedPublicKeyWithPrefix.removing05PrefixIfNeeded()
- return Data(hex: hexEncodedPublicKey)
- }()
-
- // MARK: Error
- internal enum Error : LocalizedError {
- case symmetricKeyGenerationFailed
- case endpointParsingFailed
- case proxyResponseParsingFailed
- case fileServerHTTPError(code: Int, message: Any?)
-
- internal var errorDescription: String? {
- switch self {
- case .symmetricKeyGenerationFailed: return "Couldn't generate symmetric key."
- case .endpointParsingFailed: return "Couldn't parse endpoint."
- case .proxyResponseParsingFailed: return "Couldn't parse file server proxy response."
- case .fileServerHTTPError(let httpStatusCode, let message): return "File server returned \(httpStatusCode) with description: \(message ?? "no description provided.")."
- }
- }
- }
-
- // MARK: Initialization
- internal init(for server: String) {
- self.server = server
- super.init()
- }
-
- // MARK: Proxying
- override internal func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> SnodeAPI.RawResponsePromise {
- let isLokiFileServer = (server == FileServerAPI.server)
- guard isLokiFileServer else { return super.perform(request, withCompletionQueue: queue) } // Don't proxy open group requests for now
- return performLokiFileServerNSURLRequest(request, withCompletionQueue: queue)
- }
-
- internal func performLokiFileServerNSURLRequest(_ request: NSURLRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> SnodeAPI.RawResponsePromise {
- var headers = getCanonicalHeaders(for: request)
- return Promise { [server = self.server, keyPair = self.keyPair, httpSession = self.httpSession] seal in
- DispatchQueue.global(qos: .userInitiated).async {
- let uncheckedSymmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: LokiFileServerProxy.fileServerPublicKey, privateKey: keyPair.privateKey)
- guard let symmetricKey = uncheckedSymmetricKey else { return seal.reject(Error.symmetricKeyGenerationFailed) }
- SnodeAPI.getRandomSnode().then2 { proxy -> Promise in
- let url = "\(proxy.address):\(proxy.port)/file_proxy"
- guard let urlAsString = request.url?.absoluteString, let serverURLEndIndex = urlAsString.range(of: server)?.upperBound,
- serverURLEndIndex < urlAsString.endIndex else { throw Error.endpointParsingFailed }
- let endpointStartIndex = urlAsString.index(after: serverURLEndIndex)
- let endpoint = String(urlAsString[endpointStartIndex.. SnodeAPI.RawResponsePromise {
- return TSNetworkManager.shared().perform(request, withCompletionQueue: queue).map2 { $0.responseObject }.recover2 { error -> SnodeAPI.RawResponsePromise in
- throw HTTPError.from(error: error) ?? error
- }
- }
-
- internal func getCanonicalHeaders(for request: NSURLRequest) -> [String:Any] {
- guard let headers = request.allHTTPHeaderFields else { return [:] }
- return headers.mapValues { value in
- switch value.lowercased() {
- case "true": return true
- case "false": return false
- default: return value
- }
- }
- }
-}
-
-// MARK: - HTTP Error
-
-public extension LokiHTTPClient {
-
- public enum HTTPError : LocalizedError {
- case networkError(code: Int, response: Any?, underlyingError: Error?)
-
- internal static func from(error: Error) -> LokiHTTPClient.HTTPError? {
- if let error = error as? NetworkManagerError {
- if case NetworkManagerError.taskError(_, let underlyingError) = error, let nsError = underlyingError as? NSError {
- var response = nsError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]
- // Deserialize response if needed
- if let data = response as? Data, let json = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON {
- response = json
- }
- return LokiHTTPClient.HTTPError.networkError(code: error.statusCode, response: response, underlyingError: underlyingError)
- }
- return LokiHTTPClient.HTTPError.networkError(code: error.statusCode, response: nil, underlyingError: error)
- }
- return nil
- }
-
- public var errorDescription: String? {
- switch self {
- case .networkError(let code, let body, let underlyingError): return underlyingError?.localizedDescription ?? "HTTP request failed with status code: \(code), message: \(body ?? "nil")."
- }
- }
-
- internal var statusCode: Int {
- switch self {
- case .networkError(let code, _, _): return code
- }
- }
-
- internal var isNetworkError: Bool {
- switch self {
- case .networkError(_, _, let underlyingError): return underlyingError != nil && IsNSErrorNetworkFailure(underlyingError)
- }
- }
- }
-}
diff --git a/SignalServiceKit/src/Loki/API/DotNetAPI.swift b/SignalServiceKit/src/Loki/API/DotNetAPI.swift
index 9883e69bd..7a3bd0cee 100644
--- a/SignalServiceKit/src/Loki/API/DotNetAPI.swift
+++ b/SignalServiceKit/src/Loki/API/DotNetAPI.swift
@@ -68,7 +68,10 @@ public class DotNetAPI : NSObject {
let queryParameters = "pubKey=\(getUserHexEncodedPublicKey())"
let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")!
let request = TSRequest(url: url)
- return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global(qos: .default)).map2 { rawResponse in
+ let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise { $0.fulfill(server) } : PublicChatAPI.getOpenGroupServerPublicKey(for: server)
+ return serverPublicKeyPromise.then2 { serverPublicKey in
+ OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey)
+ }.map2 { rawResponse in
guard let json = rawResponse as? JSON, let base64EncodedChallenge = json["cipherText64"] as? String, let base64EncodedServerPublicKey = json["serverPubKey64"] as? String,
let challenge = Data(base64Encoded: base64EncodedChallenge), var serverPublicKey = Data(base64Encoded: base64EncodedServerPublicKey) else {
throw DotNetAPIError.parsingFailed
@@ -92,7 +95,10 @@ public class DotNetAPI : NSObject {
let url = URL(string: "\(server)/loki/v1/submit_challenge")!
let parameters = [ "pubKey" : getUserHexEncodedPublicKey(), "token" : token ]
let request = TSRequest(url: url, method: "POST", parameters: parameters)
- return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global(qos: .default)).map2 { _ in token }
+ let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise { $0.fulfill(server) } : PublicChatAPI.getOpenGroupServerPublicKey(for: server)
+ return serverPublicKeyPromise.then2 { serverPublicKey in
+ OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey)
+ }.map2 { _ in token }
}
// MARK: Public API
@@ -126,8 +132,7 @@ public class DotNetAPI : NSObject {
data = unencryptedAttachmentData
}
// Check the file size if needed
- let isLokiFileServer = (server == FileServerAPI.server)
- if isLokiFileServer && data.count > FileServerAPI.maxFileSize {
+ if data.count > FileServerAPI.maxFileSize {
return seal.reject(DotNetAPIError.maxFileSizeExceeded)
}
// Create the request
@@ -143,10 +148,16 @@ public class DotNetAPI : NSObject {
return seal.reject(error)
}
// Send the request
- func parseResponse(_ responseObject: Any) {
+ let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise { $0.fulfill(FileServerAPI.fileServerPublicKey) }
+ : PublicChatAPI.getOpenGroupServerPublicKey(for: server)
+ attachment.isUploaded = false
+ attachment.save()
+ let _ = serverPublicKeyPromise.then2 { serverPublicKey in
+ OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey)
+ }.done2 { json in
// Parse the server ID & download URL
- guard let json = responseObject as? JSON, let data = json["data"] as? JSON, let serverID = data["id"] as? UInt64, let downloadURL = data["url"] as? String else {
- print("[Loki] Couldn't parse attachment from: \(responseObject).")
+ guard let data = json["data"] as? JSON, let serverID = data["id"] as? UInt64, let downloadURL = data["url"] as? String else {
+ print("[Loki] Couldn't parse attachment from: \(json).")
return seal.reject(DotNetAPIError.parsingFailed)
}
// Update the attachment
@@ -155,38 +166,8 @@ public class DotNetAPI : NSObject {
attachment.downloadURL = downloadURL
attachment.save()
seal.fulfill(())
- }
- let isProxyingRequired = (server == FileServerAPI.server) // Don't proxy open group requests for now
- if isProxyingRequired {
- attachment.isUploaded = false
- attachment.save()
- let _ = LokiFileServerProxy(for: server).performLokiFileServerNSURLRequest(request as NSURLRequest).done2 { responseObject in
- parseResponse(responseObject)
- }.catch2 { error in
- seal.reject(error)
- }
- } else {
- let task = AFURLSessionManager(sessionConfiguration: .default).uploadTask(withStreamedRequest: request as URLRequest, progress: { rawProgress in
- // Broadcast progress updates
- let progress = max(0.1, rawProgress.fractionCompleted)
- let userInfo: [String:Any] = [ kAttachmentUploadProgressKey : progress, kAttachmentUploadAttachmentIDKey : attachmentID ]
- DispatchQueue.main.async {
- NotificationCenter.default.post(name: .attachmentUploadProgress, object: nil, userInfo: userInfo)
- }
- }, completionHandler: { response, responseObject, error in
- if let error = error {
- print("[Loki] Couldn't upload attachment due to error: \(error).")
- return seal.reject(error)
- }
- let statusCode = (response as! HTTPURLResponse).statusCode
- let isSuccessful = (200...299) ~= statusCode
- guard isSuccessful else {
- print("[Loki] Couldn't upload attachment.")
- return seal.reject(DotNetAPIError.generic)
- }
- parseResponse(responseObject)
- })
- task.resume()
+ }.catch2 { error in
+ seal.reject(error)
}
}
if server == FileServerAPI.server {
@@ -210,7 +191,7 @@ internal extension Promise {
internal func handlingInvalidAuthTokenIfNeeded(for server: String) -> Promise {
return recover2 { error -> Promise in
- if let error = error as? NetworkManagerError, (error.statusCode == 401 || error.statusCode == 403) {
+ if case HTTP.Error.httpRequestFailed(let statusCode, _) = error, statusCode == 401 || statusCode == 403 {
print("[Loki] Auth token for: \(server) expired; dropping it.")
DotNetAPI.clearAuthToken(for: server)
}
diff --git a/SignalServiceKit/src/Loki/API/FileServerAPI.swift b/SignalServiceKit/src/Loki/API/FileServerAPI.swift
index adae9bcfc..e2652f7dd 100644
--- a/SignalServiceKit/src/Loki/API/FileServerAPI.swift
+++ b/SignalServiceKit/src/Loki/API/FileServerAPI.swift
@@ -4,21 +4,14 @@ import PromiseKit
public final class FileServerAPI : DotNetAPI {
// MARK: Settings
- private static let deviceLinkType = "network.loki.messenger.devicemapping"
private static let attachmentType = "net.app.core.oembed"
+ private static let deviceLinkType = "network.loki.messenger.devicemapping"
+
+ internal static let fileServerPublicKey = "62509D59BDEEC404DD0D489C1E15BA8F94FD3D619B01C1BF48A9922BFCB7311C"
public static let maxFileSize = 10_000_000 // 10 MB
@objc public static let server = "https://file.getsession.org"
-
- internal static var useOnionRequests = true
-
- private static let fileServerPublicKey: String = {
- let base64EncodedPublicKey = "BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc"
- let publicKeyWithPrefix = Data(base64Encoded: base64EncodedPublicKey)!
- let hexEncodedPublicKeyWithPrefix = publicKeyWithPrefix.toHexString()
- return hexEncodedPublicKeyWithPrefix.removing05PrefixIfNeeded()
- }()
// MARK: Storage
override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" }
@@ -51,69 +44,52 @@ public final class FileServerAPI : DotNetAPI {
public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set) -> Promise> {
let hexEncodedPublicKeysDescription = "[ \(hexEncodedPublicKeys.joined(separator: ", ")) ]"
print("[Loki] Getting device links for: \(hexEncodedPublicKeysDescription).")
-
- func handleRawResponseForDeviceLinks(rawResponse: JSON, data: [JSON]) -> Set {
- return Set(data.flatMap { data -> [DeviceLink] in
- guard let annotations = data["annotations"] as? [JSON], !annotations.isEmpty else { return [] }
- guard let annotation = annotations.first(where: { $0["type"] as? String == deviceLinkType }),
- let value = annotation["value"] as? JSON, let rawDeviceLinks = value["authorisations"] as? [JSON],
- let hexEncodedPublicKey = data["username"] as? String else {
- print("[Loki] Couldn't parse device links from: \(rawResponse).")
- return []
- }
- return rawDeviceLinks.compactMap { rawDeviceLink in
- guard let masterPublicKey = rawDeviceLink["primaryDevicePubKey"] as? String, let slavePublicKey = rawDeviceLink["secondaryDevicePubKey"] as? String,
- let base64EncodedSlaveSignature = rawDeviceLink["requestSignature"] as? String else {
- print("[Loki] Couldn't parse device link for user: \(hexEncodedPublicKey) from: \(rawResponse).")
- return nil
- }
- let masterSignature: Data?
- if let base64EncodedMasterSignature = rawDeviceLink["grantSignature"] as? String {
- masterSignature = Data(base64Encoded: base64EncodedMasterSignature)
- } else {
- masterSignature = nil
- }
- let slaveSignature = Data(base64Encoded: base64EncodedSlaveSignature)
- let master = DeviceLink.Device(publicKey: masterPublicKey, signature: masterSignature)
- let slave = DeviceLink.Device(publicKey: slavePublicKey, signature: slaveSignature)
- let deviceLink = DeviceLink(between: master, and: slave)
- if let masterSignature = masterSignature {
- guard DeviceLinkingUtilities.hasValidMasterSignature(deviceLink) else {
- print("[Loki] Received a device link with an invalid master signature.")
- return nil
- }
- }
- guard DeviceLinkingUtilities.hasValidSlaveSignature(deviceLink) else {
- print("[Loki] Received a device link with an invalid slave signature.")
- return nil
- }
- return deviceLink
- }
- })
- }
-
return getAuthToken(for: server).then2 { token -> Promise> in
let queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1"
let url = URL(string: "\(server)/users?\(queryParameters)")!
let request = TSRequest(url: url)
- if (useOnionRequests) {
- return OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: fileServerPublicKey).map2 { rawResponse -> Set in
- guard let data = rawResponse["data"] as? [JSON] else {
- print("[Loki] Couldn't parse device links for users: \(hexEncodedPublicKeys) from: \(rawResponse).")
- throw DotNetAPIError.parsingFailed
- }
- return handleRawResponseForDeviceLinks(rawResponse: rawResponse, data: data)
- }.map2 { deviceLinks in
- storage.setDeviceLinks(deviceLinks)
- return deviceLinks
- }
- }
- return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global(qos: .default)).map2 { rawResponse -> Set in
- guard let json = rawResponse as? JSON, let data = json["data"] as? [JSON] else {
+ return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey).map2 { rawResponse -> Set in
+ guard let data = rawResponse["data"] as? [JSON] else {
print("[Loki] Couldn't parse device links for users: \(hexEncodedPublicKeys) from: \(rawResponse).")
throw DotNetAPIError.parsingFailed
}
- return handleRawResponseForDeviceLinks(rawResponse: json, data: data)
+ return Set(data.flatMap { data -> [DeviceLink] in
+ guard let annotations = data["annotations"] as? [JSON], !annotations.isEmpty else { return [] }
+ guard let annotation = annotations.first(where: { $0["type"] as? String == deviceLinkType }),
+ let value = annotation["value"] as? JSON, let rawDeviceLinks = value["authorisations"] as? [JSON],
+ let hexEncodedPublicKey = data["username"] as? String else {
+ print("[Loki] Couldn't parse device links from: \(rawResponse).")
+ return []
+ }
+ return rawDeviceLinks.compactMap { rawDeviceLink in
+ guard let masterPublicKey = rawDeviceLink["primaryDevicePubKey"] as? String, let slavePublicKey = rawDeviceLink["secondaryDevicePubKey"] as? String,
+ let base64EncodedSlaveSignature = rawDeviceLink["requestSignature"] as? String else {
+ print("[Loki] Couldn't parse device link for user: \(hexEncodedPublicKey) from: \(rawResponse).")
+ return nil
+ }
+ let masterSignature: Data?
+ if let base64EncodedMasterSignature = rawDeviceLink["grantSignature"] as? String {
+ masterSignature = Data(base64Encoded: base64EncodedMasterSignature)
+ } else {
+ masterSignature = nil
+ }
+ let slaveSignature = Data(base64Encoded: base64EncodedSlaveSignature)
+ let master = DeviceLink.Device(publicKey: masterPublicKey, signature: masterSignature)
+ let slave = DeviceLink.Device(publicKey: slavePublicKey, signature: slaveSignature)
+ let deviceLink = DeviceLink(between: master, and: slave)
+ if let masterSignature = masterSignature {
+ guard DeviceLinkingUtilities.hasValidMasterSignature(deviceLink) else {
+ print("[Loki] Received a device link with an invalid master signature.")
+ return nil
+ }
+ }
+ guard DeviceLinkingUtilities.hasValidSlaveSignature(deviceLink) else {
+ print("[Loki] Received a device link with an invalid slave signature.")
+ return nil
+ }
+ return deviceLink
+ }
+ })
}.map2 { deviceLinks in
storage.setDeviceLinks(deviceLinks)
return deviceLinks
@@ -134,10 +110,7 @@ public final class FileServerAPI : DotNetAPI {
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return attempt(maxRetryCount: 8, recoveringOn: SnodeAPI.workQueue) {
- if (useOnionRequests) {
- return OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: fileServerPublicKey).map2 { _ in }
- }
- return LokiFileServerProxy(for: server).perform(request).map2 { _ in }
+ OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey).map2 { _ in }
}.handlingInvalidAuthTokenIfNeeded(for: server).recover2 { error in
print("Couldn't update device links due to error: \(error).")
throw error
@@ -193,19 +166,9 @@ public final class FileServerAPI : DotNetAPI {
print("[Loki] Couldn't upload profile picture due to error: \(error).")
return Promise(error: error)
}
- if (useOnionRequests) {
- return OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: fileServerPublicKey).map2 { json in
- guard let data = json["data"] as? JSON, let downloadURL = data["url"] as? String else {
- print("[Loki] Couldn't parse profile picture from: \(json).")
- throw DotNetAPIError.parsingFailed
- }
- UserDefaults.standard[.lastProfilePictureUpload] = Date()
- return downloadURL
- }
- }
- return LokiFileServerProxy(for: server).performLokiFileServerNSURLRequest(request as NSURLRequest).map2 { responseObject in
- guard let json = responseObject as? JSON, let data = json["data"] as? JSON, let downloadURL = data["url"] as? String else {
- print("[Loki] Couldn't parse profile picture from: \(responseObject).")
+ return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey).map2 { json in
+ guard let data = json["data"] as? JSON, let downloadURL = data["url"] as? String else {
+ print("[Loki] Couldn't parse profile picture from: \(json).")
throw DotNetAPIError.parsingFailed
}
UserDefaults.standard[.lastProfilePictureUpload] = Date()
@@ -214,21 +177,21 @@ public final class FileServerAPI : DotNetAPI {
}
// MARK: Open Group Server Public Key
- public static func getOpenGroupKey(for openGroupServer: String) -> Promise {
- let serverURL = URL(string: openGroupServer)!
- let url = URL(string: "\(server)/loki/v1/getOpenGroupKey/\(serverURL.host!)")!
+ public static func getPublicKey(for openGroupServer: String) -> Promise {
+ let url = URL(string: "\(server)/loki/v1/getOpenGroupKey/\(URL(string: openGroupServer)!.host!)")!
let request = TSRequest(url: url)
- let token = "loki" // tokenless request, using a dummy token
+ let token = "loki" // Tokenless request; use a dummy token
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
- return OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: fileServerPublicKey).map2 { rawResponse in
- guard let bodyAsString = rawResponse["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 }
+ return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey).map2 { 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 {
print("[Loki] Couldn't parse open group public key from: \(body).")
throw DotNetAPIError.parsingFailed
}
- let publicKeyWithPrefix = Data(base64Encoded: base64EncodedPublicKey)!
- let hexEncodedPublicKeyWithPrefix = publicKeyWithPrefix.toHexString()
- return hexEncodedPublicKeyWithPrefix.removing05PrefixIfNeeded()
+ let prefixedPublicKey = Data(base64Encoded: base64EncodedPublicKey)!
+ let hexEncodedPrefixedPublicKey = prefixedPublicKey.toHexString()
+ return hexEncodedPrefixedPublicKey.removing05PrefixIfNeeded()
}
}
}
diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift
index 12e1b6aa0..b3bc3cc25 100644
--- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift
+++ b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift
@@ -18,9 +18,9 @@ extension OnionRequestAPI {
}
/// - Note: Sync. Don't call from the main thread.
- private static func encrypt(_ plaintext: Data, using x25519Key: String?) throws -> EncryptionResult {
+ private static func encrypt(_ plaintext: Data, using hexEncodedX25519PublicKey: String?) throws -> EncryptionResult {
guard !Thread.isMainThread else { preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") }
- guard let hexEncodedX25519PublicKey = x25519Key else { throw Error.snodePublicKeySetMissing }
+ guard let hexEncodedX25519PublicKey = hexEncodedX25519PublicKey else { throw Error.snodePublicKeySetMissing }
let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey)
let ephemeralKeyPair = Curve25519.generateKeyPair()
let ephemeralSharedSecret = try Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: ephemeralKeyPair.privateKey)
@@ -30,24 +30,26 @@ extension OnionRequestAPI {
return (ciphertext, Data(bytes: symmetricKey), ephemeralKeyPair.publicKey)
}
- /// Encrypts `payload` for `snode` and returns the result. Use this to build the core of an onion request.
- internal static func encrypt(_ payload: JSON, using x25519Key: String?, to destination: JSON) -> Promise {
+ /// Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request.
+ internal static func encrypt(_ payload: JSON, for destination: Destination) -> Promise {
let (promise, seal) = Promise.pending()
DispatchQueue.global(qos: .userInitiated).async {
do {
- // The wrapper is not needed when it is a file server onion request
guard JSONSerialization.isValidJSONObject(payload) else { return seal.reject(HTTP.Error.invalidJSON) }
- if let destination = destination["destination"] {
+ // Wrapping isn't needed for file server or open group onion requests
+ switch destination {
+ case .snode(let snode):
+ guard let snodeX25519PublicKey = snode.publicKeySet?.x25519Key else { return seal.reject(Error.snodePublicKeySetMissing) }
let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ])
let payloadAsString = String(data: payloadAsData, encoding: .utf8)! // Snodes only accept this as a string
let wrapper: JSON = [ "body" : payloadAsString, "headers" : "" ]
guard JSONSerialization.isValidJSONObject(wrapper) else { return seal.reject(HTTP.Error.invalidJSON) }
let plaintext = try JSONSerialization.data(withJSONObject: wrapper, options: [ .fragmentsAllowed ])
- let result = try encrypt(plaintext, using: x25519Key)
+ let result = try encrypt(plaintext, using: snodeX25519PublicKey)
seal.fulfill(result)
- } else {
+ case .server(_, let serverX25519PublicKey):
let plaintext = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ])
- let result = try encrypt(plaintext, using: x25519Key)
+ let result = try encrypt(plaintext, using: serverX25519PublicKey)
seal.fulfill(result)
}
} catch (let error) {
@@ -58,16 +60,31 @@ extension OnionRequestAPI {
}
/// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request.
- internal static func encryptHop(with x25519Key: String?, to destination: JSON, using previousEncryptionResult: EncryptionResult) -> Promise {
+ internal static func encryptHop(from lhs: Destination, to rhs: Destination, using previousEncryptionResult: EncryptionResult) -> Promise {
let (promise, seal) = Promise.pending()
DispatchQueue.global(qos: .userInitiated).async {
- var parameters = destination
+ var parameters: JSON
+ switch rhs {
+ case .snode(let snode):
+ guard let snodeED25519PublicKey = snode.publicKeySet?.ed25519Key else { return seal.reject(Error.snodePublicKeySetMissing) }
+ parameters = [ "destination" : snodeED25519PublicKey ]
+ case .server(let host, _):
+ parameters = [ "host" : host, "target" : "/loki/v1/lsrpc", "method" : "POST" ]
+ }
parameters["ciphertext"] = previousEncryptionResult.ciphertext.base64EncodedString()
parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString()
+ let x25519PublicKey: String
+ switch lhs {
+ case .snode(let snode):
+ guard let snodeX25519PublicKey = snode.publicKeySet?.x25519Key else { return seal.reject(Error.snodePublicKeySetMissing) }
+ x25519PublicKey = snodeX25519PublicKey
+ case .server(_, let serverX25519PublicKey):
+ x25519PublicKey = serverX25519PublicKey
+ }
do {
guard JSONSerialization.isValidJSONObject(parameters) else { return seal.reject(HTTP.Error.invalidJSON) }
let plaintext = try JSONSerialization.data(withJSONObject: parameters, options: [ .fragmentsAllowed ])
- let result = try encrypt(plaintext, using: x25519Key)
+ let result = try encrypt(plaintext, using: x25519PublicKey)
seal.fulfill(result)
} catch (let error) {
seal.reject(error)
diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift
index 403e7ad47..8f9e57372 100644
--- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift
+++ b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift
@@ -19,10 +19,17 @@ public enum OnionRequestAPI {
private static var guardSnodeCount: UInt { return pathCount } // One per path
+ // MARK: Destination
+ internal enum Destination {
+ case snode(Snode)
+ case server(host: String, x25519PublicKey: String)
+ }
+
// MARK: Error
internal enum Error : LocalizedError {
case httpRequestFailedAtTargetSnode(statusCode: UInt, json: JSON)
case insufficientSnodes
+ case invalidURL
case missingSnodeVersion
case randomDataGenerationFailed
case snodePublicKeySetMissing
@@ -32,6 +39,7 @@ public enum OnionRequestAPI {
switch self {
case .httpRequestFailedAtTargetSnode(let statusCode): return "HTTP request failed at target snode with status code: \(statusCode)."
case .insufficientSnodes: return "Couldn't find enough snodes to build a path."
+ case .invalidURL: return "Invalid URL"
case .missingSnodeVersion: return "Missing snode version."
case .randomDataGenerationFailed: return "Couldn't generate random data."
case .snodePublicKeySetMissing: return "Missing snode public key set."
@@ -138,7 +146,7 @@ public enum OnionRequestAPI {
/// Returns a `Path` to be used for building an onion request. Builds new paths as needed.
///
/// - Note: Exposed for testing purposes.
- internal static func getPath(excluding snode: Snode?) -> Promise {
+ private static func getPath(excluding snode: Snode?) -> Promise {
guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") }
if paths.count < pathCount {
let storage = OWSPrimaryStorage.shared()
@@ -181,34 +189,29 @@ public enum OnionRequestAPI {
}
/// Builds an onion around `payload` and returns the result.
- private static func buildOnion(around payload: JSON, targetedAt snode: Snode?, to destination: JSON = [:], using x25519Key: String? = nil) -> Promise {
+ private static func buildOnion(around payload: JSON, targetedAt destination: Destination) -> Promise {
var guardSnode: Snode!
- var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the target snode
+ var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination
var encryptionResult: EncryptionResult!
- return getPath(excluding: snode).then2 { path -> Promise in
+ var snodeToExclude: Snode?
+ if case .snode(let snode) = destination { snodeToExclude = snode }
+ return getPath(excluding: snodeToExclude).then2 { path -> Promise in
guardSnode = path.first!
- // Encrypt in reverse order, i.e. the target snode first
- var dest = destination
- var x25519PublicKey = x25519Key
- if let snode = snode {
- dest = [ "destination": snode.publicKeySet!.ed25519Key ]
- x25519PublicKey = snode.publicKeySet?.x25519Key
- }
- return encrypt(payload, using: x25519PublicKey, to: dest).then2 { r -> Promise in
+ // Encrypt in reverse order, i.e. the destination first
+ return encrypt(payload, for: destination).then2 { r -> Promise in
targetSnodeSymmetricKey = r.symmetricKey
// Recursively encrypt the layers of the onion (again in reverse order)
encryptionResult = r
var path = path
- var destination = dest
+ var rhs = destination
func addLayer() -> Promise {
if path.isEmpty {
return Promise { $0.fulfill(encryptionResult) }
} else {
- let lhs = path.removeLast()
- let x25519Key = lhs.publicKeySet?.x25519Key
- return OnionRequestAPI.encryptHop(with: x25519Key, to: destination, using: encryptionResult).then2 { r -> Promise in
+ let lhs = Destination.snode(path.removeLast())
+ return OnionRequestAPI.encryptHop(from: lhs, to: rhs, using: encryptionResult).then2 { r -> Promise in
encryptionResult = r
- destination = [ "destination": lhs.publicKeySet!.ed25519Key ]
+ rhs = lhs
return addLayer()
}
}
@@ -219,47 +222,48 @@ public enum OnionRequestAPI {
}
// MARK: Internal API
- internal static func getCanonicalHeaders(for request: NSURLRequest) -> [String:Any] {
- guard let headers = request.allHTTPHeaderFields else { return [:] }
- return headers.mapValues { value in
- switch value.lowercased() {
- case "true": return true
- case "false": return false
- default: return value
- }
- }
- }
-
/// Sends an onion request to `snode`. Builds new paths as needed.
- internal static func sendOnionRequestSnodeDest(invoking method: Snode.Method, on snode: Snode, with parameters: JSON, associatedWith publicKey: String) -> Promise {
+ internal static func sendOnionRequest(to snode: Snode, invoking method: Snode.Method, with parameters: JSON, associatedWith publicKey: String) -> Promise {
let payload: JSON = [ "method" : method.rawValue, "params" : parameters ]
- let promise = sendOnionRequest(on: snode, with: payload, to: [:], using: nil, associatedWith: publicKey)
- promise.recover2 { error -> Promise in
+ return sendOnionRequest(with: payload, to: Destination.snode(snode), associatedWith: publicKey).recover2 { error -> Promise in
guard case OnionRequestAPI.Error.httpRequestFailedAtTargetSnode(let statusCode, let json) = error else { throw error }
throw SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode, associatedWith: publicKey) ?? error
}
- return promise
}
- /// Sends an onion request to `file server`. Builds new paths as needed.
- internal static func sendOnionRequestLsrpcDest(_ request: NSURLRequest, server: String, using x25519Key: String, noJSON: Bool = false) -> Promise {
- var headers = getCanonicalHeaders(for: request)
- let urlAsString = request.url!.absoluteString
- let serverURLEndIndex = urlAsString.range(of: server)!.upperBound
+ /// Sends an onion request to `server`. Builds new paths as needed.
+ internal static func sendOnionRequest(_ request: NSURLRequest, to server: String, using x25519Key: String, isJSONRequired: Bool = true) -> Promise {
+ let rawHeaders = request.allHTTPHeaderFields ?? [:]
+ var headers: JSON = rawHeaders.mapValues { value in
+ switch value.lowercased() {
+ case "true": return true
+ case "false": return false
+ default: return value
+ }
+ }
+ guard let url = request.url?.absoluteString, let host = request.url?.host else { return Promise(error: Error.invalidURL) }
var endpoint = ""
- if server.count < urlAsString.count {
- let endpointStartIndex = urlAsString.index(after: serverURLEndIndex)
- endpoint = String(urlAsString[endpointStartIndex.. Promise in
- // TODO: File Server API handle Error
- throw error
+ let destination = Destination.server(host: host, x25519PublicKey: x25519Key)
+ let promise = sendOnionRequest(with: payload, to: destination, associatedWith: getUserHexEncodedPublicKey(), isJSONRequired: isJSONRequired)
+ promise.catch2 { error in
+ print("[Loki] [Onion Request API] Couldn't reach server: \(server) due to error: \(error).")
}
return promise
}
- internal static func sendOnionRequest(on snode: Snode?, with payload: JSON, to destination: JSON, using x25519Key: String?, associatedWith publicKey: String, noJSON: Bool = false) -> Promise {
+ internal static func sendOnionRequest(with payload: JSON, to destination: Destination, associatedWith publicKey: String, isJSONRequired: Bool = true) -> Promise {
let (promise, seal) = Promise.pending()
var guardSnode: Snode!
DispatchQueue.global(qos: .userInitiated).async {
- buildOnion(around: payload, targetedAt: snode, to: destination, using: x25519Key).done2 { intermediate in
+ buildOnion(around: payload, targetedAt: destination).done2 { intermediate in
guardSnode = intermediate.guardSnode
let url = "\(guardSnode.address):\(guardSnode.port)/onion_req"
let finalEncryptionResult = intermediate.finalEncryptionResult
@@ -310,18 +311,16 @@ public enum OnionRequestAPI {
print("[Loki] The user's clock is out of sync with the service node network.")
seal.reject(SnodeAPI.SnodeAPIError.clockOutOfSync)
} else {
-
- if noJSON {
- let body = ["body": bodyAsString]
- guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtTargetSnode(statusCode: UInt(statusCode), json: body)) }
- seal.fulfill(body)
-
+ let body: JSON
+ if !isJSONRequired {
+ body = [ "result" : bodyAsString ]
} else {
guard let bodyAsData = bodyAsString.data(using: .utf8),
- let body = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) }
- guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtTargetSnode(statusCode: UInt(statusCode), json: body)) }
- seal.fulfill(body)
+ let b = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) }
+ body = b
}
+ guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtTargetSnode(statusCode: UInt(statusCode), json: body)) }
+ seal.fulfill(body)
}
} catch (let error) {
seal.reject(error)
diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift
index 07be2fdb1..d5af0ec4e 100644
--- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift
+++ b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift
@@ -7,9 +7,7 @@ public final class PublicChatAPI : DotNetAPI {
@objc public static let defaultChats: [PublicChat] = [] // Currently unused
public static var displayNameUpdatees: [String:Set] = [:]
-
- public static var useOnionRequests = true
-
+
// MARK: Settings
private static let attachmentType = "net.app.core.oembed"
private static let channelInfoType = "net.patter-app.settings"
@@ -32,28 +30,6 @@ public final class PublicChatAPI : DotNetAPI {
@objc public static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection"
@objc public static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection"
- @objc public static let openGroupPublicKeyCollection = "LokiGroupChatPublicKeyCollection"
-
- private static func getOpenGroupPublicKey(on server: String) -> String? {
- var result: String? = nil
- storage.dbReadConnection.read { transaction in
- result = transaction.object(forKey: "\(server)", inCollection: openGroupPublicKeyCollection) as! String?
- }
- return result
- }
-
- private static func setOpneGroupPublicKey(on server: String, value publicKey: String) {
- try! Storage.writeSync { transaction in
- transaction.setObject(publicKey, forKey: "\(server)", inCollection: openGroupPublicKeyCollection)
- }
- }
-
- private static func removeOpenGroupPublicKey(on server: String) {
- try! Storage.writeSync { transaction in
- transaction.removeObject(forKey: "\(server)", inCollection: openGroupPublicKeyCollection)
- }
- }
-
private static func getLastMessageServerID(for group: UInt64, on server: String) -> UInt? {
var result: UInt? = nil
storage.dbReadConnection.read { transaction in
@@ -97,20 +73,24 @@ public final class PublicChatAPI : DotNetAPI {
public static func clearCaches(for channel: UInt64, on server: String) {
removeLastMessageServerID(for: channel, on: server)
removeLastDeletionServerID(for: channel, on: server)
- removeOpenGroupPublicKey(on: server)
+ try! Storage.writeSync { transaction in
+ Storage.removeOpenGroupPublicKey(for: server, using: transaction)
+ }
}
// MARK: Open Group Public Key Validation
- public static func getOpenGroupServerPublicKey(on server: String) -> Promise {
- if let publicKey = getOpenGroupPublicKey(on: server) {
+ public static func getOpenGroupServerPublicKey(for server: String) -> Promise {
+ if let publicKey = Storage.getOpenGroupPublicKey(for: server) {
return Promise.value(publicKey)
} else {
- return FileServerAPI.getOpenGroupKey(for: server).then2 { hexEncodedPublicKey -> Promise in
+ 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.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey, noJSON: true).map2 { _ -> String in
- setOpneGroupPublicKey(on: server, value: hexEncodedPublicKey)
- return hexEncodedPublicKey
+ return OnionRequestAPI.sendOnionRequest(request, to: server, using: publicKey, isJSONRequired: false).map(on: DispatchQueue.global(qos: .default)) { _ -> String in
+ try! Storage.writeSync { transaction in
+ Storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
+ }
+ return publicKey
}
}
}
@@ -123,98 +103,88 @@ public final class PublicChatAPI : DotNetAPI {
}
public static func getMessages(for channel: UInt64, on server: String) -> Promise<[PublicChatMessage]> {
-
- func handleMessages(rawResponse: Any) throws -> [PublicChatMessage] {
- guard let json = rawResponse as? JSON, let rawMessages = json["data"] as? [JSON] else {
- print("[Loki] Couldn't parse messages for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
- throw DotNetAPIError.parsingFailed
- }
- return rawMessages.flatMap { message in
- let isDeleted = (message["is_deleted"] as? Int == 1)
- guard !isDeleted else { return nil }
- guard let annotations = message["annotations"] as? [JSON], let annotation = annotations.first(where: { $0["type"] as? String == publicChatMessageType }), 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 else {
- print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(message).")
- return nil
- }
- var profilePicture: PublicChatMessage.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 = PublicChatMessage.ProfilePicture(profileKey: profileKey, url: url)
- }
- let lastMessageServerID = getLastMessageServerID(for: channel, on: server)
- if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: channel, on: server, to: serverID) }
- let quote: PublicChatMessage.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 = PublicChatMessage.Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteePublicKey: quoteePublicKey, quotedMessageBody: quotedMessageBody,
- quotedMessageServerID: quotedMessageServerID)
- } else {
- quote = nil
- }
- let signature = PublicChatMessage.Signature(data: Data(hex: hexEncodedSignatureData), version: signatureVersion)
- let attachmentsAsJSON = annotations.filter { $0["type"] as? String == attachmentType }
- let attachments: [PublicChatMessage.Attachment] = attachmentsAsJSON.compactMap { attachmentAsJSON in
- guard let value = attachmentAsJSON["value"] as? JSON, let kindAsString = value["lokiType"] as? String, let kind = PublicChatMessage.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 {
- print("[Loki] Ignoring public chat message with invalid link preview.")
- return nil
- }
- }
- return PublicChatMessage.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 = PublicChatMessage(serverID: serverID, senderPublicKey: hexEncodedPublicKey, displayName: displayName, profilePicture: profilePicture,
- body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature)
- guard result.hasValidSignature() else {
- print("[Loki] Ignoring public chat message with invalid signature.")
- return nil
- }
- var existingMessageID: String? = nil
- storage.dbReadConnection.read { transaction in
- existingMessageID = storage.getIDForMessage(withServerID: UInt(result.serverID!), in: transaction)
- }
- guard existingMessageID == nil else {
- print("[Loki] Ignoring duplicate public chat message.")
- return nil
- }
- return result
- }.sorted { $0.timestamp < $1.timestamp }
- }
-
var queryParameters = "include_annotations=1"
if let lastMessageServerID = getLastMessageServerID(for: channel, on: server) {
queryParameters += "&since_id=\(lastMessageServerID)"
} else {
queryParameters += "&count=\(fallbackBatchCount)&include_deleted=0"
}
- return getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<[PublicChatMessage]> in
- let url = URL(string: "\(server)/channels/\(channel)/messages?\(queryParameters)")!
- let request = TSRequest(url: url)
- request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublickey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublickey).map2 { rawResponse in
- return try handleMessages(rawResponse: rawResponse)
+ return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in
+ getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<[PublicChatMessage]> 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)) { rawResponse in
+ guard let json = rawResponse as? JSON, let rawMessages = json["data"] as? [JSON] else {
+ print("[Loki] Couldn't parse messages for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
+ throw DotNetAPIError.parsingFailed
}
+ return rawMessages.flatMap { message in
+ let isDeleted = (message["is_deleted"] as? Int == 1)
+ guard !isDeleted else { return nil }
+ guard let annotations = message["annotations"] as? [JSON], let annotation = annotations.first(where: { $0["type"] as? String == publicChatMessageType }), 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 else {
+ print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(message).")
+ return nil
+ }
+ var profilePicture: PublicChatMessage.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 = PublicChatMessage.ProfilePicture(profileKey: profileKey, url: url)
+ }
+ let lastMessageServerID = getLastMessageServerID(for: channel, on: server)
+ if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: channel, on: server, to: serverID) }
+ let quote: PublicChatMessage.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 = PublicChatMessage.Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteePublicKey: quoteePublicKey, quotedMessageBody: quotedMessageBody,
+ quotedMessageServerID: quotedMessageServerID)
+ } else {
+ quote = nil
+ }
+ let signature = PublicChatMessage.Signature(data: Data(hex: hexEncodedSignatureData), version: signatureVersion)
+ let attachmentsAsJSON = annotations.filter { $0["type"] as? String == attachmentType }
+ let attachments: [PublicChatMessage.Attachment] = attachmentsAsJSON.compactMap { attachmentAsJSON in
+ guard let value = attachmentAsJSON["value"] as? JSON, let kindAsString = value["lokiType"] as? String, let kind = PublicChatMessage.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 {
+ print("[Loki] Ignoring public chat message with invalid link preview.")
+ return nil
+ }
+ }
+ return PublicChatMessage.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 = PublicChatMessage(serverID: serverID, senderPublicKey: hexEncodedPublicKey, displayName: displayName, profilePicture: profilePicture,
+ body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature)
+ guard result.hasValidSignature() else {
+ print("[Loki] Ignoring public chat message with invalid signature.")
+ return nil
+ }
+ var existingMessageID: String? = nil
+ storage.dbReadConnection.read { transaction in
+ existingMessageID = storage.getIDForMessage(withServerID: UInt(result.serverID!), in: transaction)
+ }
+ guard existingMessageID == nil else {
+ print("[Loki] Ignoring duplicate public chat message.")
+ return nil
+ }
+ return result
+ }.sorted { $0.timestamp < $1.timestamp }
}
}
- return LokiFileServerProxy(for: server).perform(request).map(on: DispatchQueue.global(qos: .default)) { rawResponse in
- return try handleMessages(rawResponse: rawResponse)
- }
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
@@ -225,41 +195,31 @@ public final class PublicChatAPI : DotNetAPI {
}
public static func sendMessage(_ message: PublicChatMessage, to channel: UInt64, on server: String) -> Promise {
-
- func handleSendMessageResult(rawResponse: Any, with displayName: String, for signedMessage: PublicChatMessage) throws -> PublicChatMessage {
- // ISO8601DateFormatter doesn't support milliseconds before iOS 11
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
- guard let json = rawResponse as? JSON, 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 {
- print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
- throw DotNetAPIError.parsingFailed
- }
- let timestamp = UInt64(date.timeIntervalSince1970) * 1000
- return PublicChatMessage(serverID: serverID, senderPublicKey: getUserHexEncodedPublicKey(), displayName: displayName, profilePicture: signedMessage.profilePicture, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature)
- }
-
print("[Loki] Sending message to public chat channel with ID: \(channel) on server: \(server).")
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(DotNetAPIError.signingFailed) }
attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
- 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 displayName = userDisplayName
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).map2 { rawResponse in
- return try handleSendMessageResult(rawResponse: rawResponse, with: displayName, for: signedMessage)
+ 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 displayName = userDisplayName
+ return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { rawResponse in
+ // ISO8601DateFormatter doesn't support milliseconds before iOS 11
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
+ guard let json = rawResponse as? JSON, 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 {
+ print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
+ throw DotNetAPIError.parsingFailed
}
+ let timestamp = UInt64(date.timeIntervalSince1970) * 1000
+ return PublicChatMessage(serverID: serverID, senderPublicKey: getUserHexEncodedPublicKey(), displayName: displayName, profilePicture: signedMessage.profilePicture, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature)
}
}
- return LokiFileServerProxy(for: server).perform(request).map(on: DispatchQueue.global(qos: .default)) { rawResponse in
- return try handleSendMessageResult(rawResponse: rawResponse, with: displayName, for: signedMessage)
- }
}.handlingInvalidAuthTokenIfNeeded(for: server)
}.done(on: DispatchQueue.global(qos: .default)) { message in
seal.fulfill(message)
@@ -272,23 +232,6 @@ public final class PublicChatAPI : DotNetAPI {
// MARK: Deletion
public static func getDeletedMessageServerIDs(for channel: UInt64, on server: String) -> Promise<[UInt64]> {
-
- func handleDeletedMessageServerIDs(rawResponse: Any) throws -> [UInt64] {
- guard let json = rawResponse as? JSON, let deletions = json["data"] as? [JSON] else {
- print("[Loki] Couldn't parse deleted messages for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
- throw DotNetAPIError.parsingFailed
- }
- return deletions.flatMap { deletion in
- guard let serverID = deletion["id"] as? UInt64, let messageServerID = deletion["message_id"] as? UInt64 else {
- print("[Loki] Couldn't parse deleted message for public chat channel with ID: \(channel) on server: \(server) from: \(deletion).")
- return nil
- }
- let lastDeletionServerID = getLastDeletionServerID(for: channel, on: server)
- if serverID > (lastDeletionServerID ?? 0) { setLastDeletionServerID(for: channel, on: server, to: serverID) }
- return messageServerID
- }
- }
-
print("[Loki] Getting deleted messages for public chat channel with ID: \(channel) on server: \(server).")
let queryParameters: String
if let lastDeletionServerID = getLastDeletionServerID(for: channel, on: server) {
@@ -296,20 +239,27 @@ public final class PublicChatAPI : DotNetAPI {
} else {
queryParameters = "count=\(fallbackBatchCount)"
}
- return 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)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).map2 { rawResponse in
- return try handleDeletedMessageServerIDs(rawResponse: rawResponse)
+ 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)) { rawResponse in
+ guard let json = rawResponse as? JSON, let deletions = json["data"] as? [JSON] else {
+ print("[Loki] Couldn't parse deleted messages for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
+ throw DotNetAPIError.parsingFailed
+ }
+ return deletions.flatMap { deletion in
+ guard let serverID = deletion["id"] as? UInt64, let messageServerID = deletion["message_id"] as? UInt64 else {
+ print("[Loki] Couldn't parse deleted message for public chat channel with ID: \(channel) on server: \(server) from: \(deletion).")
+ return nil
+ }
+ let lastDeletionServerID = getLastDeletionServerID(for: channel, on: server)
+ if serverID > (lastDeletionServerID ?? 0) { setLastDeletionServerID(for: channel, on: server, to: serverID) }
+ return messageServerID
}
}
}
- return LokiFileServerProxy(for: server).perform(request).map(on: DispatchQueue.global(qos: .default)) { rawResponse in
- return try handleDeletedMessageServerIDs(rawResponse: rawResponse)
- }
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
@@ -323,61 +273,46 @@ public final class PublicChatAPI : DotNetAPI {
print("[Loki] Deleting message with ID: \(messageID) for public chat channel with ID: \(channel) on server: \(server) (isModerationRequest = \(isModerationRequest)).")
let urlAsString = isSentByUser ? "\(server)/channels/\(channel)/messages/\(messageID)" : "\(server)/loki/v1/moderation/message/\(messageID)"
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
- 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)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).done2 { result -> Void in
- print("[Loki] Deleted message with ID: \(messageID) on server: \(server).")
- }
+ 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).done(on: DispatchQueue.global(qos: .default)) { result -> Void in
+ print("[Loki] Deleted message with ID: \(messageID) on server: \(server).")
}
}
- return LokiFileServerProxy(for: server).perform(request).done(on: DispatchQueue.global(qos: .default)) { result -> Void in
- print("[Loki] Deleted message with ID: \(messageID) on server: \(server).")
- }
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
// MARK: Display Name & Profile Picture
public static func getDisplayNames(for channel: UInt64, on server: String) -> Promise {
-
- func handleDisplayNames(rawResponse: Any, for hexEncodedPublicKeys: Set) throws {
- guard let json = rawResponse as? JSON, let data = json["data"] as? [JSON] else {
- print("[Loki] Couldn't parse display names for users: \(hexEncodedPublicKeys) from: \(rawResponse).")
- throw DotNetAPIError.parsingFailed
- }
- try! 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.. Promise in
- let queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1"
- let url = URL(string: "\(server)/users?\(queryParameters)")!
- let request = TSRequest(url: url)
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).map2 { rawResponse in
- try handleDisplayNames(rawResponse: rawResponse, for: hexEncodedPublicKeys)
+ print("[Loki] 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)) { rawResponse in
+ guard let json = rawResponse as? JSON, let data = json["data"] as? [JSON] else {
+ print("[Loki] Couldn't parse display names for users: \(publicKeys) from: \(rawResponse).")
+ throw DotNetAPIError.parsingFailed
+ }
+ try! 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.. 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)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).map2 { _ in }.recover(on: DispatchQueue.global(qos: .default)) { error in
- print("Couldn't update display name due to error: \(error).")
- throw error
- }
+ 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
}
}
- return LokiFileServerProxy(for: server).perform(request).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)
}
}
@@ -423,22 +352,16 @@ public final class PublicChatAPI : DotNetAPI {
}
let parameters: JSON = [ "annotations" : [ annotation ] ]
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
- 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)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).map2 { _ in }.recover(on: DispatchQueue.global(qos: .default)) { error in
- print("[Loki] Couldn't update profile picture due to error: \(error).")
- throw error
- }
+ 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("[Loki] Couldn't update profile picture due to error: \(error).")
+ throw error
}
}
- return LokiFileServerProxy(for: server).perform(request).map(on: DispatchQueue.global(qos: .default)) { _ in }.recover(on: DispatchQueue.global(qos: .default)) { error in
- print("[Loki] Couldn't update profile picture due to error: \(error).")
- throw error
- }
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
@@ -461,10 +384,10 @@ public final class PublicChatAPI : DotNetAPI {
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 avatarURL = info.profilePictureURL {
+ if let profilePictureURL = info.profilePictureURL {
let configuration = URLSessionConfiguration.default
let manager = AFURLSessionManager.init(sessionConfiguration: configuration)
- let url = URL(string: "\(server)\(avatarURL)")!
+ let url = URL(string: "\(server)\(profilePictureURL)")!
let request = URLRequest(url: url)
let task = manager.downloadTask(with: request, progress: nil,
destination: { (targetPath: URL, response: URLResponse) -> URL in
@@ -495,84 +418,64 @@ public final class PublicChatAPI : DotNetAPI {
}
public static func getInfo(for channel: UInt64, on server: String) -> Promise {
-
- func handleInfo(rawResponse: Any) throws -> PublicChatInfo {
- guard let json = rawResponse as? JSON,
- 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 {
- print("[Loki] Couldn't parse info for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
- throw DotNetAPIError.parsingFailed
- }
- let storage = OWSPrimaryStorage.shared()
- try! Storage.writeSync { transaction in
- storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction)
- }
- let publicChatInfo = PublicChatInfo(displayName: displayName, profilePictureURL: profilePictureURL, memberCount: memberCount)
- updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo)
- return publicChatInfo
- }
-
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
- 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)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- return OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).map2 { rawResponse in
- return try handleInfo(rawResponse: rawResponse)
+ 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)) { rawResponse in
+ guard let json = rawResponse as? JSON,
+ 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 {
+ print("[Loki] Couldn't parse info for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
+ throw DotNetAPIError.parsingFailed
+ }
+ let storage = OWSPrimaryStorage.shared()
+ try! Storage.writeSync { transaction in
+ storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction)
}
+ let publicChatInfo = PublicChatInfo(displayName: displayName, profilePictureURL: profilePictureURL, memberCount: memberCount)
+ updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo)
+ return publicChatInfo
}
}
- return LokiFileServerProxy(for: server).perform(request).map(on: DispatchQueue.global(qos: .default)) { rawResponse in
- return try handleInfo(rawResponse: rawResponse)
- }
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
public static func join(_ channel: UInt64, on server: String) -> Promise {
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) {
- 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)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).done2 { result -> Void in
- print("[Loki] Joined channel with ID: \(channel) on server: \(server).")
- }
+ 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)) { result -> Void in
+ print("[Loki] Joined channel with ID: \(channel) on server: \(server).")
}
}
- return LokiFileServerProxy(for: server).perform(request).done(on: DispatchQueue.global(qos: .default)) { result -> Void in
- print("[Loki] 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)) {
- 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)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).done2 { result -> Void in
- print("[Loki] Left channel with ID: \(channel) on server: \(server).")
- }
+ 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)) { result -> Void in
+ print("[Loki] Left channel with ID: \(channel) on server: \(server).")
}
}
- return LokiFileServerProxy(for: server).perform(request).done(on: DispatchQueue.global(qos: .default)) { result -> Void in
- print("[Loki] Left channel with ID: \(channel) on server: \(server).")
- }
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
}
@@ -587,45 +490,32 @@ public final class PublicChatAPI : DotNetAPI {
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
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).map2 { _ in}
- }
+ 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}
}
- return LokiFileServerProxy(for: server).perform(request).map(on: DispatchQueue.global(qos: .default)) { _ in }
}
// MARK: Moderators
public static func getModerators(for channel: UInt64, on server: String) -> Promise> {
-
- func handleModerators(rawResponse: Any) throws -> Set {
- guard let json = rawResponse as? JSON, let moderators = json["moderators"] as? [String] else {
- print("[Loki] Couldn't parse moderators for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
- throw DotNetAPIError.parsingFailed
- }
- let moderatorsAsSet = Set(moderators);
- if self.moderators.keys.contains(server) {
- self.moderators[server]![channel] = moderatorsAsSet
- } else {
- self.moderators[server] = [ channel : moderatorsAsSet ]
- }
- return moderatorsAsSet
- }
-
- return 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)" ]
- if (useOnionRequests) {
- return getOpenGroupServerPublicKey(on: server).then2 { hexEncodedPublicKey in
- OnionRequestAPI.sendOnionRequestLsrpcDest(request, server: server, using: hexEncodedPublicKey).map2 { rawResponse in
- return try handleModerators(rawResponse: rawResponse)
+ 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)) { rawResponse in
+ guard let json = rawResponse as? JSON, let moderators = json["moderators"] as? [String] else {
+ print("[Loki] Couldn't parse moderators for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
+ throw DotNetAPIError.parsingFailed
+ }
+ let moderatorsAsSet = Set(moderators);
+ if self.moderators.keys.contains(server) {
+ self.moderators[server]![channel] = moderatorsAsSet
+ } else {
+ self.moderators[server] = [ channel : moderatorsAsSet ]
}
+ return moderatorsAsSet
}
}
- return LokiFileServerProxy(for: server).perform(request).map(on: DispatchQueue.global(qos: .default)) { rawResponse in
- return try handleModerators(rawResponse: rawResponse)
- }
}.handlingInvalidAuthTokenIfNeeded(for: server)
}
diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatManager.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatManager.swift
index 331b0acc3..1fe3e6fc6 100644
--- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatManager.swift
+++ b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatManager.swift
@@ -56,20 +56,7 @@ public final class PublicChatManager : NSObject {
return Promise(error: Error.chatCreationFailed)
}
}
- if (PublicChatAPI.useOnionRequests) {
- return PublicChatAPI.getOpenGroupServerPublicKey(on: server).then2 { publicKey in
- return PublicChatAPI.getAuthToken(for: server).then2 { token in
- return PublicChatAPI.getInfo(for: channel, on: server)
- }.map2 { channelInfo -> PublicChat in
- guard let chat = self.addChat(server: server, channel: channel, name: channelInfo.displayName) else { throw Error.chatCreationFailed }
- return chat
- }
- }
- }
- // TODO: Remove this when we use onion request totally
- return PublicChatAPI.getAuthToken(for: server).then2 { token in
- return PublicChatAPI.getInfo(for: channel, on: server)
- }.map2 { channelInfo -> PublicChat in
+ return PublicChatAPI.getInfo(for: channel, on: server).map2 { channelInfo -> PublicChat in
guard let chat = self.addChat(server: server, channel: channel, name: channelInfo.displayName) else { throw Error.chatCreationFailed }
return chat
}
diff --git a/SignalServiceKit/src/Loki/API/Open Groups/Storage+PublicChats.swift b/SignalServiceKit/src/Loki/API/Open Groups/Storage+PublicChats.swift
new file mode 100644
index 000000000..204588abf
--- /dev/null
+++ b/SignalServiceKit/src/Loki/API/Open Groups/Storage+PublicChats.swift
@@ -0,0 +1,22 @@
+
+public extension Storage {
+
+ // MARK: Open Group Public Keys
+ internal static let openGroupPublicKeyCollection = "LokiOpenGroupPublicKeyCollection"
+
+ internal static func getOpenGroupPublicKey(for server: String) -> String? {
+ var result: String? = nil
+ read { transaction in
+ result = transaction.object(forKey: server, inCollection: openGroupPublicKeyCollection) as? String
+ }
+ return result
+ }
+
+ internal static func setOpenGroupPublicKey(for server: String, to publicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
+ transaction.setObject(publicKey, forKey: server, inCollection: openGroupPublicKeyCollection)
+ }
+
+ internal static func removeOpenGroupPublicKey(for server: String, using transaction: YapDatabaseReadWriteTransaction) {
+ transaction.removeObject(forKey: server, inCollection: openGroupPublicKeyCollection)
+ }
+}
diff --git a/SignalServiceKit/src/Loki/API/Shelved/LokiRSSFeed.swift b/SignalServiceKit/src/Loki/API/Shelved/LokiRSSFeed.swift
deleted file mode 100644
index 96daef891..000000000
--- a/SignalServiceKit/src/Loki/API/Shelved/LokiRSSFeed.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-
-@objc(LKRSSFeed)
-public final class LokiRSSFeed : NSObject {
- @objc public let id: String
- @objc public let server: String
- @objc public let displayName: String
- @objc public let isDeletable: Bool
-
- @objc public init(id: String, server: String, displayName: String, isDeletable: Bool) {
- self.id = "rss://\(id)"
- self.server = server
- self.displayName = displayName
- self.isDeletable = isDeletable
- }
-
- override public var description: String { return displayName }
-}
diff --git a/SignalServiceKit/src/Loki/API/Shelved/LokiRSSFeedProxy.swift b/SignalServiceKit/src/Loki/API/Shelved/LokiRSSFeedProxy.swift
deleted file mode 100644
index e58360592..000000000
--- a/SignalServiceKit/src/Loki/API/Shelved/LokiRSSFeedProxy.swift
+++ /dev/null
@@ -1,26 +0,0 @@
-import PromiseKit
-
-public enum LokiRSSFeedProxy {
-
- public enum Error : LocalizedError {
- case proxyResponseParsingFailed
-
- public var errorDescription: String? {
- switch self {
- case .proxyResponseParsingFailed: return "Couldn't parse RSS feed proxy response."
- }
- }
- }
-
- public static func fetchContent(for url: String) -> Promise {
- let server = FileServerAPI.server
- let endpoints = [ "messenger-updates/feed" : "loki/v1/rss/messenger", "loki.network/feed" : "loki/v1/rss/loki" ]
- let endpoint = endpoints.first { url.lowercased().contains($0.key) }!.value
- let url = URL(string: server + "/" + endpoint)!
- let request = TSRequest(url: url)
- return LokiFileServerProxy(for: server).perform(request).map2 { response -> String in
- guard let json = response as? JSON, let xml = json["data"] as? String else { throw Error.proxyResponseParsingFailed }
- return xml
- }
- }
-}
diff --git a/SignalServiceKit/src/Loki/API/SnodeAPI.swift b/SignalServiceKit/src/Loki/API/SnodeAPI.swift
index 9c0dba1a0..1ad7c9f8b 100644
--- a/SignalServiceKit/src/Loki/API/SnodeAPI.swift
+++ b/SignalServiceKit/src/Loki/API/SnodeAPI.swift
@@ -47,7 +47,7 @@ public final class SnodeAPI : NSObject {
// MARK: Core
internal static func invoke(_ method: Snode.Method, on snode: Snode, associatedWith publicKey: String, parameters: JSON) -> RawResponsePromise {
if useOnionRequests {
- return OnionRequestAPI.sendOnionRequestSnodeDest(invoking: method, on: snode, with: parameters, associatedWith: publicKey).map2 { $0 as Any }
+ return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey).map2 { $0 as Any }
} else {
let url = "\(snode.address):\(snode.port)/storage_rpc/v1"
return HTTP.execute(.post, url, parameters: parameters).map2 { $0 as Any }.recover2 { error -> Promise in
diff --git a/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift b/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift
index f866dfe11..52f8a4516 100644
--- a/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift
+++ b/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift
@@ -1,11 +1,11 @@
import PromiseKit
-internal enum HTTP {
+public enum HTTP {
private static let urlSession = URLSession(configuration: .ephemeral, delegate: urlSessionDelegate, delegateQueue: nil)
private static let urlSessionDelegate = URLSessionDelegateImplementation()
// MARK: Settings
- private static let timeout: TimeInterval = 20
+ public static let timeout: TimeInterval = 20
// MARK: URL Session Delegate Implementation
private final class URLSessionDelegateImplementation : NSObject, URLSessionDelegate {
@@ -17,7 +17,7 @@ internal enum HTTP {
}
// MARK: Verb
- internal enum Verb : String {
+ public enum Verb : String {
case get = "GET"
case put = "PUT"
case post = "POST"
@@ -25,12 +25,12 @@ internal enum HTTP {
}
// MARK: Error
- internal enum Error : LocalizedError {
+ public enum Error : LocalizedError {
case generic
case httpRequestFailed(statusCode: UInt, json: JSON?)
case invalidJSON
- var errorDescription: String? {
+ public var errorDescription: String? {
switch self {
case .generic: return "An error occurred."
case .httpRequestFailed(let statusCode, _): return "HTTP request failed with status code: \(statusCode)."
@@ -40,7 +40,7 @@ internal enum HTTP {
}
// MARK: Main
- internal static func execute(_ verb: Verb, _ url: String, parameters: JSON? = nil, timeout: TimeInterval = HTTP.timeout) -> Promise {
+ public static func execute(_ verb: Verb, _ url: String, parameters: JSON? = nil, timeout: TimeInterval = HTTP.timeout) -> Promise {
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = verb.rawValue
if let parameters = parameters {