Implement file server proxying

This commit is contained in:
Niels Andriesse 2020-02-02 11:33:34 +11:00
parent 8ba452fb56
commit 51fb4ed21d
9 changed files with 196 additions and 89 deletions

2
Pods

@ -1 +1 @@
Subproject commit d7ebdadef0ed419082789e6a1f65a75ff791996e
Subproject commit 9837a2fa9828fcd261233cff21dc688dc308c6b8

View File

@ -1,5 +1,6 @@
import PromiseKit
/// Base class for `LokiStorageAPI` and `LokiPublicChatAPI`.
public class LokiDotNetAPI : NSObject {
// MARK: Convenience
@ -129,7 +130,7 @@ public class LokiDotNetAPI : NSObject {
let queryParameters = "pubKey=\(userHexEncodedPublicKey)"
let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")!
let request = TSRequest(url: url)
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { 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 Error.parsingFailed
@ -153,7 +154,7 @@ public class LokiDotNetAPI : NSObject {
let url = URL(string: "\(server)/loki/v1/submit_challenge")!
let parameters = [ "pubKey" : userHexEncodedPublicKey, "token" : token ]
let request = TSRequest(url: url, method: "POST", parameters: parameters)
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in token }
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in token }
}
// MARK: Attachments (Public Obj-C API)

View File

@ -0,0 +1,106 @@
import PromiseKit
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 proxy response."
case .fileServerHTTPError(let httpStatusCode, let message): return "File server returned error \(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) -> LokiAPI.RawResponsePromise {
let uncheckedSymmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: LokiFileServerProxy.fileServerPublicKey, privateKey: keyPair.privateKey)
guard let symmetricKey = uncheckedSymmetricKey else { return Promise(error: Error.symmetricKeyGenerationFailed) }
let headers = getCanonicalHeaders(for: request)
return LokiAPI.getRandomSnode().then { [server = self.server, keyPair = self.keyPair, httpSession = self.httpSession] proxy -> Promise<Any> in
let url = "\(proxy.address):\(proxy.port)/proxy"
print("[Loki] Proxying request to \(server) through \(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..<urlAsString.endIndex])
let parametersAsData = try JSONSerialization.data(withJSONObject: request.parameters, options: [])
let proxyRequestParameters: JSON = [
"body" : String(bytes: parametersAsData, encoding: .utf8),
"endpoint": endpoint,
"method" : request.httpMethod,
"headers" : headers
]
let proxyRequestParametersAsData = try JSONSerialization.data(withJSONObject: proxyRequestParameters, options: [])
let ivAndCipherText = try DiffieHellman.encrypt(proxyRequestParametersAsData, using: symmetricKey)
let proxyRequestHeaders = [
"X-Loki-File-Server-Target" : "/loki/v1/secure_rpc",
"X-Loki-File-Server-Verb" : "POST",
"X-Loki-File-Server-Headers" : "{ X-Loki-File-Server-Ephemeral-Key : \(keyPair.publicKey.base64EncodedString()) }",
"Connection" : "close"
]
let (promise, resolver) = LokiAPI.RawResponsePromise.pending()
let proxyRequest = AFHTTPRequestSerializer().request(withMethod: "POST", urlString: url, parameters: nil, error: nil)
proxyRequest.allHTTPHeaderFields = proxyRequestHeaders
proxyRequest.httpBody = try JSONSerialization.data(withJSONObject: [ "cipherText64" : ivAndCipherText.base64EncodedString() ], options: [])
proxyRequest.timeoutInterval = request.timeoutInterval
var task: URLSessionDataTask!
task = httpSession.dataTask(with: proxyRequest as URLRequest) { response, result, error in
if let error = error {
let nmError = NetworkManagerError.taskError(task: task, underlyingError: error)
let nsError: NSError = nmError as NSError
nsError.isRetryable = false
resolver.reject(nsError)
} else {
resolver.fulfill(result)
}
}
task.resume()
return promise
}.map { rawResponse in
guard let data = rawResponse as? Data, !data.isEmpty, let cipherText = Data(base64Encoded: data) else {
print("[Loki] Received a non-string encoded response.")
return rawResponse
}
let response = try DiffieHellman.decrypt(cipherText, using: symmetricKey)
let uncheckedJSON = try? JSONSerialization.jsonObject(with: response, options: .allowFragments) as? JSON
guard let json = uncheckedJSON, let httpStatusCode = json["status"] as? Int else { throw HTTPError.networkError(code: -1, response: nil, underlyingError: Error.proxyResponseParsingFailed) }
let isSuccess = (200..<300).contains(httpStatusCode)
var body: Any? = nil
if let bodyAsString = json["body"] as? String {
body = bodyAsString
if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? [String: Any] {
body = bodyAsJSON
}
}
guard isSuccess else { throw HTTPError.networkError(code: httpStatusCode, response: body, underlyingError: Error.fileServerHTTPError(code: httpStatusCode, message: body)) }
return body
}.recover { error -> Promise<Any> in
print("[Loki] Proxy request failed with error: \(error.localizedDescription).")
throw HTTPError.from(error: error) ?? error
}
}
}

View File

@ -1,50 +1,74 @@
import PromiseKit
/// Base class for `LokiSnodeProxy`, `LokiFileServerProxy` and `LokiRSSFeedProxy`.
internal class LokiHTTPClient {
internal enum HTTPError: LocalizedError {
case networkError(code: Int, response: Any?, underlyingError: Error?)
public var errorDescription: String? {
switch self {
case .networkError(let code, let body, let underlingError): return underlingError?.localizedDescription ?? "Failed HTTP request with status code: \(code), message: \(body ?? "")."
}
}
}
internal lazy var httpSession: AFHTTPSessionManager = {
let result = AFHTTPSessionManager(sessionConfiguration: .ephemeral)
let securityPolicy = AFSecurityPolicy.default()
securityPolicy.allowInvalidCertificates = true
securityPolicy.validatesDomainName = false
result.securityPolicy = securityPolicy
result.responseSerializer = AFHTTPResponseSerializer()
return result
}()
internal func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> LokiAPI.RawResponsePromise {
return TSNetworkManager.shared().perform(request, withCompletionQueue: queue).map { $0.responseObject }.recover { error -> LokiAPI.RawResponsePromise in
throw HTTPError.from(error: error) ?? error
}
}
}
internal extension LokiHTTPClient.HTTPError {
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: []) as? JSON {
response = json
}
return LokiHTTPClient.HTTPError.networkError(code: error.statusCode, response: response, underlyingError: underlyingError)
internal func getCanonicalHeaders(for request: TSRequest) -> [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
internal extension LokiHTTPClient {
internal 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: []) 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)
}
return LokiHTTPClient.HTTPError.networkError(code: error.statusCode, response: nil, underlyingError: error)
}
return nil
}
internal var isNetworkError: Bool {
switch self {
case .networkError(_, _, let underlyingError): return underlyingError != nil && IsNSErrorNetworkFailure(underlyingError)
}
}
internal var statusCode: Int {
switch self {
case .networkError(let code, _, _): return code
}
}
}

View File

@ -1,18 +1,8 @@
import PromiseKit
internal class LokiSnodeProxy : LokiHTTPClient {
internal let target: LokiAPITarget
private let keyPair: ECKeyPair
private lazy var httpSession: AFHTTPSessionManager = {
let result = AFHTTPSessionManager(sessionConfiguration: .ephemeral)
let securityPolicy = AFSecurityPolicy.default()
securityPolicy.allowInvalidCertificates = true
securityPolicy.validatesDomainName = false
result.securityPolicy = securityPolicy
result.responseSerializer = AFHTTPResponseSerializer()
return result
}()
private let target: LokiAPITarget
private let keyPair = Curve25519.generateKeyPair()
// MARK: Error
internal enum Error : LocalizedError {
@ -23,10 +13,10 @@ internal class LokiSnodeProxy : LokiHTTPClient {
internal var errorDescription: String? {
switch self {
case .targetPublicKeySetMissing: return "Missing target public key set"
case .symmetricKeyGenerationFailed: return "Couldn't generate symmetric key"
case .proxyResponseParsingFailed: return "Couldn't parse proxy response"
case .targetSnodeHTTPError(let httpStatusCode, let message): return "Target snode returned error \(httpStatusCode) with description: \(message ?? "no description provided")."
case .targetPublicKeySetMissing: return "Missing target public key set."
case .symmetricKeyGenerationFailed: return "Couldn't generate symmetric key."
case .proxyResponseParsingFailed: return "Couldn't parse proxy response."
case .targetSnodeHTTPError(let httpStatusCode, let message): return "Target snode returned error \(httpStatusCode) with description: \(message ?? "no description provided.")."
}
}
}
@ -34,7 +24,6 @@ internal class LokiSnodeProxy : LokiHTTPClient {
// MARK: Initialization
internal init(for target: LokiAPITarget) {
self.target = target
keyPair = Curve25519.generateKeyPair()
super.init()
}
@ -48,7 +37,7 @@ internal class LokiSnodeProxy : LokiHTTPClient {
let url = "\(proxy.address):\(proxy.port)/proxy"
print("[Loki] Proxying request to \(target) through \(proxy).")
let parametersAsData = try JSONSerialization.data(withJSONObject: request.parameters, options: [])
let proxyRequestParameters: [String:Any] = [
let proxyRequestParameters: JSON = [
"method" : request.httpMethod,
"body" : String(bytes: parametersAsData, encoding: .utf8),
"headers" : headers
@ -56,7 +45,7 @@ internal class LokiSnodeProxy : LokiHTTPClient {
let proxyRequestParametersAsData = try JSONSerialization.data(withJSONObject: proxyRequestParameters, options: [])
let ivAndCipherText = try DiffieHellman.encrypt(proxyRequestParametersAsData, using: symmetricKey)
let proxyRequestHeaders = [
"X-Sender-Public-Key" : keyPair.publicKey.map { String(format: "%02hhx", $0) }.joined(),
"X-Sender-Public-Key" : keyPair.publicKey.toHexString(),
"X-Target-Snode-Key" : targetHexEncodedPublicKeySet.idKey
]
let (promise, resolver) = LokiAPI.RawResponsePromise.pending()
@ -72,7 +61,6 @@ internal class LokiSnodeProxy : LokiHTTPClient {
nsError.isRetryable = false
resolver.reject(nsError)
} else {
OutageDetection.shared.reportConnectionSuccess()
resolver.fulfill(result)
}
}
@ -101,16 +89,4 @@ internal class LokiSnodeProxy : LokiHTTPClient {
throw HTTPError.from(error: error) ?? error
}
}
// MARK: Convenience
private func getCanonicalHeaders(for request: TSRequest) -> [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
}
}
}
}

View File

@ -26,9 +26,9 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
}
// MARK: Database
override internal class var authTokenCollection: String { "LokiGroupChatAuthTokenCollection" } // Should ideally be LokiPublicChatAuthTokenCollection
@objc public static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection" // Should ideally be LokiPublicChatLastMessageServerIDCollection
@objc public static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection" // Should ideally be LokiPublicChatLastDeletionServerIDCollection
override internal class var authTokenCollection: String { "LokiGroupChatAuthTokenCollection" }
@objc public static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection"
@objc public static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection"
private static func getLastMessageServerID(for group: UInt64, on server: String) -> UInt? {
var result: UInt? = nil
@ -80,7 +80,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
}
let url = URL(string: "\(server)/channels/\(channel)/messages?\(queryParameters)")!
let request = TSRequest(url: url)
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { 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 Error.parsingFailed
@ -156,7 +156,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
let request = TSRequest(url: url, method: "POST", parameters: parameters)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
let displayName = userDisplayName
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in
// ISO8601DateFormatter doesn't support milliseconds before iOS 11
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
@ -187,7 +187,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
}
let url = URL(string: "\(server)/loki/v1/channel/\(channel)/deletes?\(queryParameters)")!
let request = TSRequest(url: url)
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { 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 Error.parsingFailed
@ -212,7 +212,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
let url = URL(string: urlAsString)!
let request = TSRequest(url: url, method: "DELETE", parameters: [:])
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).done(on: DispatchQueue.global()) { result -> Void in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).done(on: DispatchQueue.global()) { result -> Void in
print("[Loki] Deleted message with ID: \(messageID) on server: \(server).")
}.retryingIfNeeded(maxRetryCount: maxRetryCount)
}
@ -221,7 +221,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
public static func getModerators(for channel: UInt64, on server: String) -> Promise<Set<String>> {
let url = URL(string: "\(server)/loki/v1/channel/\(channel)/get_moderators")!
let request = TSRequest(url: url)
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { 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 Error.parsingFailed
@ -241,7 +241,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
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 TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).done(on: DispatchQueue.global()) { result -> Void in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).done(on: DispatchQueue.global()) { result -> Void in
print("[Loki] Joined channel with ID: \(channel) on server: \(server).")
}.retryingIfNeeded(maxRetryCount: maxRetryCount)
}
@ -252,7 +252,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
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 TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).done(on: DispatchQueue.global()) { result -> Void in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).done(on: DispatchQueue.global()) { result -> Void in
print("[Loki] Left channel with ID: \(channel) on server: \(server).")
}.retryingIfNeeded(maxRetryCount: maxRetryCount)
}
@ -264,7 +264,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
let url = URL(string: "\(server)/channels/\(channel)/subscribers?\(queryParameters)")!
let request = TSRequest(url: url)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in
guard let json = rawResponse as? JSON, let users = json["data"] as? [JSON] else {
print("[Loki] Couldn't parse user count for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
throw Error.parsingFailed
@ -288,7 +288,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
let queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1"
let url = URL(string: "\(server)/users?\(queryParameters)")!
let request = TSRequest(url: url)
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in
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 Error.parsingFailed
@ -318,7 +318,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
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 TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in }.recover(on: DispatchQueue.global()) { error in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in }.recover(on: DispatchQueue.global()) { error in
print("Couldn't update display name due to error: \(error).")
throw error
}
@ -336,7 +336,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
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 TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in }.recover(on: DispatchQueue.global()) { error in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { _ in }.recover(on: DispatchQueue.global()) { error in
print("[Loki] Couldn't update profile picture due to error: \(error).")
throw error
}
@ -346,7 +346,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
public static func getInfo(for channel: UInt64, on server: String) -> Promise<LokiPublicChatInfo> {
let url = URL(string: "\(server)/channels/\(channel)?include_annotations=1")!
let request = TSRequest(url: url)
return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse in
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map { rawResponse in
guard let json = rawResponse as? JSON,
let data = json["data"] as? JSON,
let annotations = data["annotations"] as? [JSON],

View File

@ -1,5 +1,7 @@
import PromiseKit
// TODO: Clean
@objc(LKPublicChatManager)
public final class LokiPublicChatManager : NSObject {
private let storage = OWSPrimaryStorage.shared()

View File

@ -135,11 +135,9 @@ public final class LokiPublicChatMessage : NSObject {
value["sig"] = signature.data.toHexString()
value["sigver"] = signature.version
}
if let avatar = avatar {
value["avatar"] = avatar;
}
let annotation: JSON = [ "type" : type, "value" : value ]
let attachmentAnnotations: [JSON] = attachments.map { attachment in
let type: String