mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge pull request #81 from loki-project/proxying
File Server & RSS Feed Proxying
This commit is contained in:
commit
2c15cd06f5
12 changed files with 266 additions and 129 deletions
2
Pods
2
Pods
|
@ -1 +1 @@
|
||||||
Subproject commit d7ebdadef0ed419082789e6a1f65a75ff791996e
|
Subproject commit 0518320cbc8571118b8a10decf72497134e96185
|
|
@ -28,41 +28,44 @@ public final class LokiRSSFeedPoller : NSObject {
|
||||||
|
|
||||||
private func poll() {
|
private func poll() {
|
||||||
let feed = self.feed
|
let feed = self.feed
|
||||||
let url = URL(string: feed.server)!
|
let url = feed.server
|
||||||
FeedParser(URL: url).parseAsync { wrapper in
|
let _ = LokiRSSFeedProxy.fetchContent(for: url).done { xml in
|
||||||
guard case .rss(let x) = wrapper, let items = x.items else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") }
|
guard let data = xml.data(using: String.Encoding.utf8) else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") }
|
||||||
items.reversed().forEach { item in
|
FeedParser(data: data).parseAsync { wrapper in
|
||||||
guard let title = item.title, let description = item.description, let date = item.pubDate else { return }
|
guard case .rss(let x) = wrapper, let items = x.items else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") }
|
||||||
let timestamp = UInt64(date.timeIntervalSince1970 * 1000)
|
items.reversed().forEach { item in
|
||||||
let urlRegex = try! NSRegularExpression(pattern: "<a\\s+(?:[^>]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>")
|
guard let title = item.title, let description = item.description, let date = item.pubDate else { return }
|
||||||
var bodyAsHTML = "\(title)<br><br>\(description)".replacingOccurrences(of: "</p>", with: "</p><br>")
|
let timestamp = UInt64(date.timeIntervalSince1970 * 1000)
|
||||||
while true {
|
let urlRegex = try! NSRegularExpression(pattern: "<a\\s+(?:[^>]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>")
|
||||||
guard let match = urlRegex.firstMatch(in: bodyAsHTML, options: [], range: NSRange(location: 0, length: bodyAsHTML.utf16.count)) else { break }
|
var bodyAsHTML = "\(title)<br><br>\(description)".replacingOccurrences(of: "</p>", with: "</p><br>")
|
||||||
let matchRange = match.range(at: 0)
|
while true {
|
||||||
let urlRange = match.range(at: 1)
|
guard let match = urlRegex.firstMatch(in: bodyAsHTML, options: [], range: NSRange(location: 0, length: bodyAsHTML.utf16.count)) else { break }
|
||||||
let descriptionRange = match.range(at: 2)
|
let matchRange = match.range(at: 0)
|
||||||
let url = (bodyAsHTML as NSString).substring(with: urlRange)
|
let urlRange = match.range(at: 1)
|
||||||
let description = (bodyAsHTML as NSString).substring(with: descriptionRange)
|
let descriptionRange = match.range(at: 2)
|
||||||
bodyAsHTML = (bodyAsHTML as NSString).replacingCharacters(in: matchRange, with: "\(description) (\(url))") as String
|
let url = (bodyAsHTML as NSString).substring(with: urlRange)
|
||||||
}
|
let description = (bodyAsHTML as NSString).substring(with: descriptionRange)
|
||||||
guard let bodyAsData = bodyAsHTML.data(using: String.Encoding.unicode) else { return }
|
bodyAsHTML = (bodyAsHTML as NSString).replacingCharacters(in: matchRange, with: "\(description) (\(url))") as String
|
||||||
let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ]
|
}
|
||||||
guard let body = try? NSAttributedString(data: bodyAsData, options: options, documentAttributes: nil).string else { return }
|
guard let bodyAsData = bodyAsHTML.data(using: String.Encoding.unicode) else { return }
|
||||||
let id = LKGroupUtilities.getEncodedRSSFeedIDAsData(feed.id)
|
let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ]
|
||||||
let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
|
guard let body = try? NSAttributedString(data: bodyAsData, options: options, documentAttributes: nil).string else { return }
|
||||||
groupContext.setName(feed.displayName)
|
let id = LKGroupUtilities.getEncodedRSSFeedIDAsData(feed.id)
|
||||||
let dataMessage = SSKProtoDataMessage.builder()
|
let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
|
||||||
dataMessage.setTimestamp(timestamp)
|
groupContext.setName(feed.displayName)
|
||||||
dataMessage.setGroup(try! groupContext.build())
|
let dataMessage = SSKProtoDataMessage.builder()
|
||||||
dataMessage.setBody(body)
|
dataMessage.setTimestamp(timestamp)
|
||||||
let content = SSKProtoContent.builder()
|
dataMessage.setGroup(try! groupContext.build())
|
||||||
content.setDataMessage(try! dataMessage.build())
|
dataMessage.setBody(body)
|
||||||
let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp)
|
let content = SSKProtoContent.builder()
|
||||||
envelope.setSource(NSLocalizedString("Loki", comment: ""))
|
content.setDataMessage(try! dataMessage.build())
|
||||||
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
|
let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp)
|
||||||
envelope.setContent(try! content.build().serializedData())
|
envelope.setSource(NSLocalizedString("Loki", comment: ""))
|
||||||
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
|
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
|
||||||
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0)
|
envelope.setContent(try! content.build().serializedData())
|
||||||
|
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
|
||||||
|
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
|
||||||
|
/// Base class for `LokiStorageAPI` and `LokiPublicChatAPI`.
|
||||||
public class LokiDotNetAPI : NSObject {
|
public class LokiDotNetAPI : NSObject {
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
@ -129,7 +130,7 @@ public class LokiDotNetAPI : NSObject {
|
||||||
let queryParameters = "pubKey=\(userHexEncodedPublicKey)"
|
let queryParameters = "pubKey=\(userHexEncodedPublicKey)"
|
||||||
let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")!
|
let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")!
|
||||||
let request = TSRequest(url: url)
|
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,
|
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 {
|
let challenge = Data(base64Encoded: base64EncodedChallenge), var serverPublicKey = Data(base64Encoded: base64EncodedServerPublicKey) else {
|
||||||
throw Error.parsingFailed
|
throw Error.parsingFailed
|
||||||
|
@ -153,7 +154,7 @@ public class LokiDotNetAPI : NSObject {
|
||||||
let url = URL(string: "\(server)/loki/v1/submit_challenge")!
|
let url = URL(string: "\(server)/loki/v1/submit_challenge")!
|
||||||
let parameters = [ "pubKey" : userHexEncodedPublicKey, "token" : token ]
|
let parameters = [ "pubKey" : userHexEncodedPublicKey, "token" : token ]
|
||||||
let request = TSRequest(url: url, method: "POST", parameters: parameters)
|
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)
|
// MARK: Attachments (Public Obj-C API)
|
||||||
|
|
106
SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift
Normal file
106
SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift
Normal 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 \(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 isLokiFileServer = server.contains("file.lokinet.org") || server.contains("file-dev.lokinet.org")
|
||||||
|
guard isLokiFileServer else { return super.perform(request, withCompletionQueue: queue) } // Don't proxy open group requests for now
|
||||||
|
let uncheckedSymmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: LokiFileServerProxy.fileServerPublicKey, privateKey: keyPair.privateKey)
|
||||||
|
guard let symmetricKey = uncheckedSymmetricKey else { return Promise(error: Error.symmetricKeyGenerationFailed) }
|
||||||
|
var headers = getCanonicalHeaders(for: request)
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
return LokiAPI.getRandomSnode().then { [server = self.server, keyPair = self.keyPair, httpSession = self.httpSession] proxy -> Promise<Any> in
|
||||||
|
let url = "\(proxy.address):\(proxy.port)/file_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 parametersAsString = !request.parameters.isEmpty ? String(bytes: parametersAsData, encoding: .utf8)! : "null"
|
||||||
|
let proxyRequestParameters: JSON = [
|
||||||
|
"body" : parametersAsString,
|
||||||
|
"endpoint": endpoint,
|
||||||
|
"method" : request.httpMethod,
|
||||||
|
"headers" : headers
|
||||||
|
]
|
||||||
|
let proxyRequestParametersAsData = try JSONSerialization.data(withJSONObject: proxyRequestParameters, options: [])
|
||||||
|
let ivAndCipherText = try DiffieHellman.encrypt(proxyRequestParametersAsData, using: symmetricKey)
|
||||||
|
let base64EncodedPublicKey = Data(hex: keyPair.hexEncodedPublicKey).base64EncodedString() // The file server expects an 05 prefixed public key
|
||||||
|
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\" : \"\(base64EncodedPublicKey)\" }",
|
||||||
|
"Connection" : "close", // TODO: Is this necessary?
|
||||||
|
"Content-Type" : "application/json"
|
||||||
|
]
|
||||||
|
let (promise, resolver) = LokiAPI.RawResponsePromise.pending()
|
||||||
|
let proxyRequest = AFHTTPRequestSerializer().request(withMethod: "POST", urlString: url, parameters: nil, error: nil)
|
||||||
|
proxyRequest.allHTTPHeaderFields = proxyRequestHeaders
|
||||||
|
proxyRequest.httpBody = "{ \"cipherText64\" : \"\(ivAndCipherText.base64EncodedString())\" }".data(using: String.Encoding.utf8)!
|
||||||
|
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 responseAsData = rawResponse as? Data, let responseAsJSON = try? JSONSerialization.jsonObject(with: responseAsData, options: .allowFragments) as? JSON, let base64EncodedCipherText = responseAsJSON["data"] as? String,
|
||||||
|
let meta = responseAsJSON["meta"] as? JSON, let statusCode = meta["code"] as? Int, let cipherText = Data(base64Encoded: base64EncodedCipherText) else {
|
||||||
|
print("[Loki] Received an invalid response.")
|
||||||
|
throw Error.proxyResponseParsingFailed
|
||||||
|
}
|
||||||
|
let isSuccess = (200..<300).contains(statusCode)
|
||||||
|
guard isSuccess else { throw HTTPError.networkError(code: statusCode, response: nil, underlyingError: Error.fileServerHTTPError(code: statusCode, message: nil)) }
|
||||||
|
let uncheckedJSONAsData = try DiffieHellman.decrypt(cipherText, using: symmetricKey)
|
||||||
|
let uncheckedJSON = try? JSONSerialization.jsonObject(with: uncheckedJSONAsData, options: .allowFragments) as? JSON
|
||||||
|
guard let json = uncheckedJSON else { throw HTTPError.networkError(code: -1, response: nil, underlyingError: Error.proxyResponseParsingFailed) }
|
||||||
|
return json
|
||||||
|
}.recover { error -> Promise<Any> in
|
||||||
|
print("[Loki] File server proxy request failed with error: \(error.localizedDescription).")
|
||||||
|
throw HTTPError.from(error: error) ?? error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,50 +1,74 @@
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
|
||||||
|
/// Base class for `LokiSnodeProxy` and `LokiFileServerProxy`.
|
||||||
internal class LokiHTTPClient {
|
internal class LokiHTTPClient {
|
||||||
|
|
||||||
internal enum HTTPError: LocalizedError {
|
internal lazy var httpSession: AFHTTPSessionManager = {
|
||||||
case networkError(code: Int, response: Any?, underlyingError: Error?)
|
let result = AFHTTPSessionManager(sessionConfiguration: .ephemeral)
|
||||||
|
let securityPolicy = AFSecurityPolicy.default()
|
||||||
public var errorDescription: String? {
|
securityPolicy.allowInvalidCertificates = true
|
||||||
switch self {
|
securityPolicy.validatesDomainName = false
|
||||||
case .networkError(let code, let body, let underlingError): return underlingError?.localizedDescription ?? "Failed HTTP request with status code: \(code), message: \(body ?? "")."
|
result.securityPolicy = securityPolicy
|
||||||
}
|
result.responseSerializer = AFHTTPResponseSerializer()
|
||||||
}
|
return result
|
||||||
}
|
}()
|
||||||
|
|
||||||
internal func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> LokiAPI.RawResponsePromise {
|
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
|
return TSNetworkManager.shared().perform(request, withCompletionQueue: queue).map { $0.responseObject }.recover { error -> LokiAPI.RawResponsePromise in
|
||||||
throw HTTPError.from(error: error) ?? error
|
throw HTTPError.from(error: error) ?? error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal extension LokiHTTPClient.HTTPError {
|
internal func getCanonicalHeaders(for request: TSRequest) -> [String: Any] {
|
||||||
|
guard let headers = request.allHTTPHeaderFields else { return [:] }
|
||||||
internal static func from(error: Error) -> LokiHTTPClient.HTTPError? {
|
return headers.mapValues { value in
|
||||||
if let error = error as? NetworkManagerError {
|
switch value.lowercased() {
|
||||||
if case NetworkManagerError.taskError(_, let underlyingError) = error, let nsError = underlyingError as? NSError {
|
case "true": return true
|
||||||
var response = nsError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]
|
case "false": return false
|
||||||
// Deserialize response if needed
|
default: return value
|
||||||
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)
|
}
|
||||||
|
|
||||||
|
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
SignalServiceKit/src/Loki/API/LokiRSSFeedProxy.swift
Normal file
26
SignalServiceKit/src/Loki/API/LokiRSSFeedProxy.swift
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import PromiseKit
|
||||||
|
|
||||||
|
public enum LokiRSSFeedProxy {
|
||||||
|
|
||||||
|
public enum Error : LocalizedError {
|
||||||
|
case proxyResponseParsingFailed
|
||||||
|
|
||||||
|
public var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .proxyResponseParsingFailed: return "Couldn't parse proxy response."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fetchContent(for url: String) -> Promise<String> {
|
||||||
|
let server = LokiStorageAPI.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).map { response -> String in
|
||||||
|
guard let json = response as? JSON, let xml = json["data"] as? String else { throw Error.proxyResponseParsingFailed }
|
||||||
|
return xml
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,8 @@
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
|
||||||
internal class LokiSnodeProxy : LokiHTTPClient {
|
internal class LokiSnodeProxy : LokiHTTPClient {
|
||||||
internal let target: LokiAPITarget
|
private let target: LokiAPITarget
|
||||||
private let keyPair: ECKeyPair
|
private let keyPair = Curve25519.generateKeyPair()
|
||||||
|
|
||||||
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
|
|
||||||
}()
|
|
||||||
|
|
||||||
// MARK: Error
|
// MARK: Error
|
||||||
internal enum Error : LocalizedError {
|
internal enum Error : LocalizedError {
|
||||||
|
@ -23,10 +13,10 @@ internal class LokiSnodeProxy : LokiHTTPClient {
|
||||||
|
|
||||||
internal var errorDescription: String? {
|
internal var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .targetPublicKeySetMissing: return "Missing target public key set"
|
case .targetPublicKeySetMissing: return "Missing target public key set."
|
||||||
case .symmetricKeyGenerationFailed: return "Couldn't generate symmetric key"
|
case .symmetricKeyGenerationFailed: return "Couldn't generate symmetric key."
|
||||||
case .proxyResponseParsingFailed: return "Couldn't parse proxy response"
|
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 .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
|
// MARK: Initialization
|
||||||
internal init(for target: LokiAPITarget) {
|
internal init(for target: LokiAPITarget) {
|
||||||
self.target = target
|
self.target = target
|
||||||
keyPair = Curve25519.generateKeyPair()
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +37,7 @@ internal class LokiSnodeProxy : LokiHTTPClient {
|
||||||
let url = "\(proxy.address):\(proxy.port)/proxy"
|
let url = "\(proxy.address):\(proxy.port)/proxy"
|
||||||
print("[Loki] Proxying request to \(target) through \(proxy).")
|
print("[Loki] Proxying request to \(target) through \(proxy).")
|
||||||
let parametersAsData = try JSONSerialization.data(withJSONObject: request.parameters, options: [])
|
let parametersAsData = try JSONSerialization.data(withJSONObject: request.parameters, options: [])
|
||||||
let proxyRequestParameters: [String:Any] = [
|
let proxyRequestParameters: JSON = [
|
||||||
"method" : request.httpMethod,
|
"method" : request.httpMethod,
|
||||||
"body" : String(bytes: parametersAsData, encoding: .utf8),
|
"body" : String(bytes: parametersAsData, encoding: .utf8),
|
||||||
"headers" : headers
|
"headers" : headers
|
||||||
|
@ -56,7 +45,7 @@ internal class LokiSnodeProxy : LokiHTTPClient {
|
||||||
let proxyRequestParametersAsData = try JSONSerialization.data(withJSONObject: proxyRequestParameters, options: [])
|
let proxyRequestParametersAsData = try JSONSerialization.data(withJSONObject: proxyRequestParameters, options: [])
|
||||||
let ivAndCipherText = try DiffieHellman.encrypt(proxyRequestParametersAsData, using: symmetricKey)
|
let ivAndCipherText = try DiffieHellman.encrypt(proxyRequestParametersAsData, using: symmetricKey)
|
||||||
let proxyRequestHeaders = [
|
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
|
"X-Target-Snode-Key" : targetHexEncodedPublicKeySet.idKey
|
||||||
]
|
]
|
||||||
let (promise, resolver) = LokiAPI.RawResponsePromise.pending()
|
let (promise, resolver) = LokiAPI.RawResponsePromise.pending()
|
||||||
|
@ -72,45 +61,32 @@ internal class LokiSnodeProxy : LokiHTTPClient {
|
||||||
nsError.isRetryable = false
|
nsError.isRetryable = false
|
||||||
resolver.reject(nsError)
|
resolver.reject(nsError)
|
||||||
} else {
|
} else {
|
||||||
OutageDetection.shared.reportConnectionSuccess()
|
|
||||||
resolver.fulfill(result)
|
resolver.fulfill(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
task.resume()
|
task.resume()
|
||||||
return promise
|
return promise
|
||||||
}.map { rawResponse in
|
}.map { rawResponse in
|
||||||
guard let data = rawResponse as? Data, !data.isEmpty, let cipherText = Data(base64Encoded: data) else {
|
guard let responseAsData = rawResponse as? Data, let cipherText = Data(base64Encoded: responseAsData) else {
|
||||||
print("[Loki] Received a non-string encoded response.")
|
print("[Loki] Received a non-string encoded response.")
|
||||||
return rawResponse
|
return rawResponse
|
||||||
}
|
}
|
||||||
let response = try DiffieHellman.decrypt(cipherText, using: symmetricKey)
|
let response = try DiffieHellman.decrypt(cipherText, using: symmetricKey)
|
||||||
let uncheckedJSON = try? JSONSerialization.jsonObject(with: response, options: .allowFragments) as? JSON
|
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) }
|
guard let json = uncheckedJSON, let statusCode = json["status"] as? Int else { throw HTTPError.networkError(code: -1, response: nil, underlyingError: Error.proxyResponseParsingFailed) }
|
||||||
let isSuccess = (200..<300).contains(httpStatusCode)
|
let isSuccess = (200..<300).contains(statusCode)
|
||||||
var body: Any? = nil
|
var body: Any? = nil
|
||||||
if let bodyAsString = json["body"] as? String {
|
if let bodyAsString = json["body"] as? String {
|
||||||
body = bodyAsString
|
body = bodyAsString
|
||||||
if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? [String: Any] {
|
if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? JSON {
|
||||||
body = bodyAsJSON
|
body = bodyAsJSON
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guard isSuccess else { throw HTTPError.networkError(code: httpStatusCode, response: body, underlyingError: Error.targetSnodeHTTPError(code: httpStatusCode, message: body)) }
|
guard isSuccess else { throw HTTPError.networkError(code: statusCode, response: body, underlyingError: Error.targetSnodeHTTPError(code: statusCode, message: body)) }
|
||||||
return body
|
return body
|
||||||
}.recover { error -> Promise<Any> in
|
}.recover { error -> Promise<Any> in
|
||||||
print("[Loki] Proxy request failed with error: \(error.localizedDescription).")
|
print("[Loki] Proxy request failed with error: \(error.localizedDescription).")
|
||||||
throw HTTPError.from(error: error) ?? error
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,9 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Database
|
// MARK: Database
|
||||||
override internal class var authTokenCollection: String { "LokiGroupChatAuthTokenCollection" } // Should ideally be LokiPublicChatAuthTokenCollection
|
override internal class var authTokenCollection: String { "LokiGroupChatAuthTokenCollection" }
|
||||||
@objc public static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection" // Should ideally be LokiPublicChatLastMessageServerIDCollection
|
@objc public static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection"
|
||||||
@objc public static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection" // Should ideally be LokiPublicChatLastDeletionServerIDCollection
|
@objc public static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection"
|
||||||
|
|
||||||
private static func getLastMessageServerID(for group: UInt64, on server: String) -> UInt? {
|
private static func getLastMessageServerID(for group: UInt64, on server: String) -> UInt? {
|
||||||
var result: UInt? = nil
|
var result: UInt? = nil
|
||||||
|
@ -80,7 +80,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
}
|
}
|
||||||
let url = URL(string: "\(server)/channels/\(channel)/messages?\(queryParameters)")!
|
let url = URL(string: "\(server)/channels/\(channel)/messages?\(queryParameters)")!
|
||||||
let request = TSRequest(url: url)
|
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 {
|
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).")
|
print("[Loki] Couldn't parse messages for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
|
||||||
throw Error.parsingFailed
|
throw Error.parsingFailed
|
||||||
|
@ -156,7 +156,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
let request = TSRequest(url: url, method: "POST", parameters: parameters)
|
let request = TSRequest(url: url, method: "POST", parameters: parameters)
|
||||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
|
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
|
||||||
let displayName = userDisplayName
|
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
|
// ISO8601DateFormatter doesn't support milliseconds before iOS 11
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
|
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 url = URL(string: "\(server)/loki/v1/channel/\(channel)/deletes?\(queryParameters)")!
|
||||||
let request = TSRequest(url: url)
|
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 {
|
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).")
|
print("[Loki] Couldn't parse deleted messages for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
|
||||||
throw Error.parsingFailed
|
throw Error.parsingFailed
|
||||||
|
@ -212,7 +212,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
let url = URL(string: urlAsString)!
|
let url = URL(string: urlAsString)!
|
||||||
let request = TSRequest(url: url, method: "DELETE", parameters: [:])
|
let request = TSRequest(url: url, method: "DELETE", parameters: [:])
|
||||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
|
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).")
|
print("[Loki] Deleted message with ID: \(messageID) on server: \(server).")
|
||||||
}.retryingIfNeeded(maxRetryCount: maxRetryCount)
|
}.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>> {
|
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 url = URL(string: "\(server)/loki/v1/channel/\(channel)/get_moderators")!
|
||||||
let request = TSRequest(url: url)
|
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 {
|
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).")
|
print("[Loki] Couldn't parse moderators for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
|
||||||
throw Error.parsingFailed
|
throw Error.parsingFailed
|
||||||
|
@ -241,7 +241,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
let url = URL(string: "\(server)/channels/\(channel)/subscribe")!
|
let url = URL(string: "\(server)/channels/\(channel)/subscribe")!
|
||||||
let request = TSRequest(url: url, method: "POST", parameters: [:])
|
let request = TSRequest(url: url, method: "POST", parameters: [:])
|
||||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
|
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).")
|
print("[Loki] Joined channel with ID: \(channel) on server: \(server).")
|
||||||
}.retryingIfNeeded(maxRetryCount: maxRetryCount)
|
}.retryingIfNeeded(maxRetryCount: maxRetryCount)
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
let url = URL(string: "\(server)/channels/\(channel)/subscribe")!
|
let url = URL(string: "\(server)/channels/\(channel)/subscribe")!
|
||||||
let request = TSRequest(url: url, method: "DELETE", parameters: [:])
|
let request = TSRequest(url: url, method: "DELETE", parameters: [:])
|
||||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
|
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).")
|
print("[Loki] Left channel with ID: \(channel) on server: \(server).")
|
||||||
}.retryingIfNeeded(maxRetryCount: maxRetryCount)
|
}.retryingIfNeeded(maxRetryCount: maxRetryCount)
|
||||||
}
|
}
|
||||||
|
@ -264,7 +264,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
let url = URL(string: "\(server)/channels/\(channel)/subscribers?\(queryParameters)")!
|
let url = URL(string: "\(server)/channels/\(channel)/subscribers?\(queryParameters)")!
|
||||||
let request = TSRequest(url: url)
|
let request = TSRequest(url: url)
|
||||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
|
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 {
|
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).")
|
print("[Loki] Couldn't parse user count for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).")
|
||||||
throw Error.parsingFailed
|
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 queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1"
|
||||||
let url = URL(string: "\(server)/users?\(queryParameters)")!
|
let url = URL(string: "\(server)/users?\(queryParameters)")!
|
||||||
let request = TSRequest(url: url)
|
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 {
|
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).")
|
print("[Loki] Couldn't parse display names for users: \(hexEncodedPublicKeys) from: \(rawResponse).")
|
||||||
throw Error.parsingFailed
|
throw Error.parsingFailed
|
||||||
|
@ -318,7 +318,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
let url = URL(string: "\(server)/users/me")!
|
let url = URL(string: "\(server)/users/me")!
|
||||||
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
|
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
|
||||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
|
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).")
|
print("Couldn't update display name due to error: \(error).")
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
@ -336,7 +336,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
let url = URL(string: "\(server)/users/me")!
|
let url = URL(string: "\(server)/users/me")!
|
||||||
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
|
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
|
||||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
|
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).")
|
print("[Loki] Couldn't update profile picture due to error: \(error).")
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
|
||||||
public static func getInfo(for channel: UInt64, on server: String) -> Promise<LokiPublicChatInfo> {
|
public static func getInfo(for channel: UInt64, on server: String) -> Promise<LokiPublicChatInfo> {
|
||||||
let url = URL(string: "\(server)/channels/\(channel)?include_annotations=1")!
|
let url = URL(string: "\(server)/channels/\(channel)?include_annotations=1")!
|
||||||
let request = TSRequest(url: url)
|
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,
|
guard let json = rawResponse as? JSON,
|
||||||
let data = json["data"] as? JSON,
|
let data = json["data"] as? JSON,
|
||||||
let annotations = data["annotations"] as? [JSON],
|
let annotations = data["annotations"] as? [JSON],
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
|
||||||
|
// TODO: Clean
|
||||||
|
|
||||||
@objc(LKPublicChatManager)
|
@objc(LKPublicChatManager)
|
||||||
public final class LokiPublicChatManager : NSObject {
|
public final class LokiPublicChatManager : NSObject {
|
||||||
private let storage = OWSPrimaryStorage.shared()
|
private let storage = OWSPrimaryStorage.shared()
|
||||||
|
|
|
@ -135,11 +135,9 @@ public final class LokiPublicChatMessage : NSObject {
|
||||||
value["sig"] = signature.data.toHexString()
|
value["sig"] = signature.data.toHexString()
|
||||||
value["sigver"] = signature.version
|
value["sigver"] = signature.version
|
||||||
}
|
}
|
||||||
|
|
||||||
if let avatar = avatar {
|
if let avatar = avatar {
|
||||||
value["avatar"] = avatar;
|
value["avatar"] = avatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
let annotation: JSON = [ "type" : type, "value" : value ]
|
let annotation: JSON = [ "type" : type, "value" : value ]
|
||||||
let attachmentAnnotations: [JSON] = attachments.map { attachment in
|
let attachmentAnnotations: [JSON] = attachments.map { attachment in
|
||||||
let type: String
|
let type: String
|
||||||
|
|
|
@ -408,6 +408,7 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func ensureSenderCertificate(certificateExpirationPolicy: OWSUDCertificateExpirationPolicy) -> Promise<SMKSenderCertificate> {
|
public func ensureSenderCertificate(certificateExpirationPolicy: OWSUDCertificateExpirationPolicy) -> Promise<SMKSenderCertificate> {
|
||||||
|
return Promise(error: "Disabled.")
|
||||||
// If there is a valid cached sender certificate, use that.
|
// If there is a valid cached sender certificate, use that.
|
||||||
//
|
//
|
||||||
// NOTE: We use a "strict" expiration policy.
|
// NOTE: We use a "strict" expiration policy.
|
||||||
|
|
Loading…
Reference in a new issue