mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
10 KiB
Swift
248 lines
10 KiB
Swift
5 years ago
|
//
|
||
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||
|
//
|
||
|
|
||
|
import Foundation
|
||
|
import PromiseKit
|
||
|
|
||
|
@objc
|
||
|
public enum RequestMakerUDAuthError: Int, Error {
|
||
|
case udAuthFailure
|
||
|
}
|
||
|
|
||
|
public enum RequestMakerError: Error {
|
||
|
case websocketRequestError(statusCode : Int, responseData : Data?, underlyingError : Error)
|
||
|
}
|
||
|
|
||
|
@objc(OWSRequestMakerResult)
|
||
|
public class RequestMakerResult: NSObject {
|
||
|
@objc
|
||
|
public let responseObject: Any?
|
||
|
|
||
|
@objc
|
||
|
public let wasSentByUD: Bool
|
||
|
|
||
|
@objc
|
||
|
public let wasSentByWebsocket: Bool
|
||
|
|
||
|
@objc
|
||
|
public init(responseObject: Any?,
|
||
|
wasSentByUD: Bool,
|
||
|
wasSentByWebsocket: Bool) {
|
||
|
self.responseObject = responseObject
|
||
|
self.wasSentByUD = wasSentByUD
|
||
|
self.wasSentByWebsocket = wasSentByWebsocket
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// A utility class that handles:
|
||
|
//
|
||
|
// * UD auth-to-Non-UD auth failover.
|
||
|
// * Websocket-to-REST failover.
|
||
|
@objc(OWSRequestMaker)
|
||
|
public class RequestMaker: NSObject {
|
||
|
|
||
|
public typealias RequestFactoryBlock = (SMKUDAccessKey?) -> TSRequest
|
||
|
public typealias UDAuthFailureBlock = () -> Void
|
||
|
public typealias WebsocketFailureBlock = () -> Void
|
||
|
|
||
|
private let label: String
|
||
|
private let requestFactoryBlock: RequestFactoryBlock
|
||
|
private let udAuthFailureBlock: UDAuthFailureBlock
|
||
|
private let websocketFailureBlock: WebsocketFailureBlock
|
||
|
private let recipientId: String
|
||
|
private let udAccess: OWSUDAccess?
|
||
|
private let canFailoverUDAuth: Bool
|
||
|
|
||
|
@objc
|
||
|
public init(label: String,
|
||
|
requestFactoryBlock : @escaping RequestFactoryBlock,
|
||
|
udAuthFailureBlock : @escaping UDAuthFailureBlock,
|
||
|
websocketFailureBlock : @escaping WebsocketFailureBlock,
|
||
|
recipientId: String,
|
||
|
udAccess: OWSUDAccess?,
|
||
|
canFailoverUDAuth: Bool) {
|
||
|
self.label = label
|
||
|
self.requestFactoryBlock = requestFactoryBlock
|
||
|
self.udAuthFailureBlock = udAuthFailureBlock
|
||
|
self.websocketFailureBlock = websocketFailureBlock
|
||
|
self.recipientId = recipientId
|
||
|
self.udAccess = udAccess
|
||
|
self.canFailoverUDAuth = canFailoverUDAuth
|
||
|
}
|
||
|
|
||
|
// MARK: - Dependencies
|
||
|
|
||
|
private var socketManager: TSSocketManager {
|
||
|
return SSKEnvironment.shared.socketManager
|
||
|
}
|
||
|
|
||
|
private var networkManager: TSNetworkManager {
|
||
|
return SSKEnvironment.shared.networkManager
|
||
|
}
|
||
|
|
||
|
private var udManager: OWSUDManager {
|
||
|
return SSKEnvironment.shared.udManager
|
||
|
}
|
||
|
|
||
|
private var profileManager: ProfileManagerProtocol {
|
||
|
return SSKEnvironment.shared.profileManager
|
||
|
}
|
||
|
|
||
|
// MARK: -
|
||
|
|
||
|
@objc
|
||
|
public func makeRequestObjc() -> AnyPromise {
|
||
|
let promise = makeRequest()
|
||
|
.recover(on: DispatchQueue.global()) { (error: Error) -> Promise<RequestMakerResult> in
|
||
|
switch error {
|
||
|
case NetworkManagerError.taskError(_, let underlyingError):
|
||
|
throw underlyingError
|
||
|
default:
|
||
|
throw error
|
||
|
}
|
||
|
}
|
||
|
let anyPromise = AnyPromise(promise)
|
||
|
anyPromise.retainUntilComplete()
|
||
|
return anyPromise
|
||
|
}
|
||
|
|
||
|
public func makeRequest() -> Promise<RequestMakerResult> {
|
||
|
return makeRequestInternal(skipUD: false, skipWebsocket: false)
|
||
|
}
|
||
|
|
||
|
private func makeRequestInternal(skipUD: Bool, skipWebsocket: Bool) -> Promise<RequestMakerResult> {
|
||
|
var udAccessForRequest: OWSUDAccess?
|
||
|
if !skipUD {
|
||
|
udAccessForRequest = udAccess
|
||
|
}
|
||
|
let isUDRequest: Bool = udAccessForRequest != nil
|
||
|
let request: TSRequest = requestFactoryBlock(udAccessForRequest?.udAccessKey)
|
||
|
let canMakeWebsocketRequests = (socketManager.canMakeRequests() && !skipWebsocket && !isUDRequest)
|
||
|
|
||
|
if canMakeWebsocketRequests {
|
||
|
return Promise { resolver in
|
||
|
socketManager.make(request, success: { (responseObject: Any?) in
|
||
|
if self.udManager.isUDVerboseLoggingEnabled() {
|
||
|
if isUDRequest {
|
||
|
Logger.debug("UD websocket request '\(self.label)' succeeded.")
|
||
|
} else {
|
||
|
Logger.debug("Non-UD websocket request '\(self.label)' succeeded.")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self.requestSucceeded(udAccess: udAccessForRequest)
|
||
|
|
||
|
resolver.fulfill(RequestMakerResult(responseObject: responseObject,
|
||
|
wasSentByUD: isUDRequest,
|
||
|
wasSentByWebsocket: true))
|
||
|
}) { (statusCode: Int, responseData: Data?, error: Error) in
|
||
|
resolver.reject(RequestMakerError.websocketRequestError(statusCode: statusCode, responseData: responseData, underlyingError: error))
|
||
|
}
|
||
|
}.recover { (error: Error) -> Promise<RequestMakerResult> in
|
||
|
switch error {
|
||
|
case RequestMakerError.websocketRequestError(let statusCode, _, _):
|
||
|
if isUDRequest && (statusCode == 401 || statusCode == 403) {
|
||
|
// If a UD request fails due to service response (as opposed to network
|
||
|
// failure), mark recipient as _not_ in UD mode, then retry.
|
||
|
self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: self.recipientId)
|
||
|
self.profileManager.fetchProfile(forRecipientId: self.recipientId)
|
||
|
self.udAuthFailureBlock()
|
||
|
|
||
|
if self.canFailoverUDAuth {
|
||
|
Logger.info("UD websocket request '\(self.label)' auth failed; failing over to non-UD websocket request.")
|
||
|
return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket)
|
||
|
} else {
|
||
|
Logger.info("UD websocket request '\(self.label)' auth failed; aborting.")
|
||
|
throw RequestMakerUDAuthError.udAuthFailure
|
||
|
}
|
||
|
}
|
||
|
break
|
||
|
default:
|
||
|
break
|
||
|
}
|
||
|
|
||
|
self.websocketFailureBlock()
|
||
|
if isUDRequest {
|
||
|
Logger.info("UD Web socket request '\(self.label)' failed; failing over to REST request: \(error).")
|
||
|
} else {
|
||
|
Logger.info("Non-UD Web socket request '\(self.label)' failed; failing over to REST request: \(error).")
|
||
|
}
|
||
|
return self.makeRequestInternal(skipUD: skipUD, skipWebsocket: true)
|
||
|
}
|
||
|
} else {
|
||
|
return self.networkManager.makePromise(request: request)
|
||
|
.map(on: DispatchQueue.global()) { (networkManagerResult: TSNetworkManager.NetworkManagerResult) -> RequestMakerResult in
|
||
|
if self.udManager.isUDVerboseLoggingEnabled() {
|
||
|
if isUDRequest {
|
||
|
Logger.debug("UD REST request '\(self.label)' succeeded.")
|
||
|
} else {
|
||
|
Logger.debug("Non-UD REST request '\(self.label)' succeeded.")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self.requestSucceeded(udAccess: udAccessForRequest)
|
||
|
|
||
|
// Unwrap the network manager promise into a request maker promise.
|
||
|
return RequestMakerResult(responseObject: networkManagerResult.responseObject,
|
||
|
wasSentByUD: isUDRequest,
|
||
|
wasSentByWebsocket: false)
|
||
|
}.recover { (error: Error) -> Promise<RequestMakerResult> in
|
||
|
switch error {
|
||
|
case NetworkManagerError.taskError(let task, _):
|
||
|
let statusCode = task.statusCode()
|
||
|
if isUDRequest && (statusCode == 401 || statusCode == 403) {
|
||
|
// If a UD request fails due to service response (as opposed to network
|
||
|
// failure), mark recipient as _not_ in UD mode, then retry.
|
||
|
self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: self.recipientId)
|
||
|
self.profileManager.fetchProfile(forRecipientId: self.recipientId)
|
||
|
self.udAuthFailureBlock()
|
||
|
|
||
|
if self.canFailoverUDAuth {
|
||
|
Logger.info("UD REST request '\(self.label)' auth failed; failing over to non-UD REST request.")
|
||
|
return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket)
|
||
|
} else {
|
||
|
Logger.info("UD REST request '\(self.label)' auth failed; aborting.")
|
||
|
throw RequestMakerUDAuthError.udAuthFailure
|
||
|
}
|
||
|
}
|
||
|
break
|
||
|
default:
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if isUDRequest {
|
||
|
Logger.debug("UD REST request '\(self.label)' failed: \(error).")
|
||
|
} else {
|
||
|
Logger.debug("Non-UD REST request '\(self.label)' failed: \(error).")
|
||
|
}
|
||
|
throw error
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private func requestSucceeded(udAccess: OWSUDAccess?) {
|
||
|
// If this was a UD request...
|
||
|
guard let udAccess = udAccess else {
|
||
|
return
|
||
|
}
|
||
|
// ...made for a user in "unknown" UD access mode...
|
||
|
guard udAccess.udAccessMode == .unknown else {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if udAccess.isRandomKey {
|
||
|
// If a UD request succeeds for an unknown user with a random key,
|
||
|
// mark recipient as .unrestricted.
|
||
|
udManager.setUnidentifiedAccessMode(.unrestricted, recipientId: recipientId)
|
||
|
} else {
|
||
|
// If a UD request succeeds for an unknown user with a non-random key,
|
||
|
// mark recipient as .enabled.
|
||
|
udManager.setUnidentifiedAccessMode(.enabled, recipientId: recipientId)
|
||
|
}
|
||
|
DispatchQueue.main.async {
|
||
|
self.profileManager.fetchProfile(forRecipientId: self.recipientId)
|
||
|
}
|
||
|
}
|
||
|
}
|