Update PromiseKit

This commit is contained in:
Michael Kirk 2018-10-13 13:21:46 -06:00
parent f578ab1df2
commit d6a6024f37
32 changed files with 347 additions and 339 deletions

View File

@ -43,16 +43,14 @@ PODS:
- Mantle (2.1.0):
- Mantle/extobjc (= 2.1.0)
- Mantle/extobjc (2.1.0)
- PromiseKit (4.5.2):
- PromiseKit/Foundation (= 4.5.2)
- PromiseKit/QuartzCore (= 4.5.2)
- PromiseKit/UIKit (= 4.5.2)
- PromiseKit/CorePromise (4.5.2)
- PromiseKit/Foundation (4.5.2):
- PromiseKit (6.5.2):
- PromiseKit/CorePromise (= 6.5.2)
- PromiseKit/Foundation (= 6.5.2)
- PromiseKit/UIKit (= 6.5.2)
- PromiseKit/CorePromise (6.5.2)
- PromiseKit/Foundation (6.5.2):
- PromiseKit/CorePromise
- PromiseKit/QuartzCore (4.5.2):
- PromiseKit/CorePromise
- PromiseKit/UIKit (4.5.2):
- PromiseKit/UIKit (6.5.2):
- PromiseKit/CorePromise
- PureLayout (3.0.2)
- Reachability (3.2)
@ -85,7 +83,7 @@ PODS:
- GRKOpenSSLFramework
- libPhoneNumber-iOS
- Mantle
- PromiseKit (~> 4.0)
- PromiseKit (~> 6.0)
- Reachability
- SAMKeychain
- SignalCoreKit
@ -101,7 +99,7 @@ PODS:
- GRKOpenSSLFramework
- libPhoneNumber-iOS
- Mantle
- PromiseKit (~> 4.0)
- PromiseKit (~> 6.0)
- Reachability
- SAMKeychain
- SignalCoreKit

View File

@ -41,7 +41,7 @@ public class MessageFetcherJob: NSObject {
guard signalService.isCensorshipCircumventionActive else {
Logger.debug("delegating message fetching to SocketManager since we're using normal transport.")
TSSocketManager.shared.requestSocketOpen()
return Promise(value: ())
return Promise.value(())
}
Logger.info("fetching messages via REST.")
@ -63,7 +63,7 @@ public class MessageFetcherJob: NSObject {
return self.run()
} else {
// All finished
return Promise(value: ())
return Promise.value(())
}
}
@ -75,7 +75,7 @@ public class MessageFetcherJob: NSObject {
@objc
@discardableResult
public func run() -> AnyPromise {
return AnyPromise(run())
return AnyPromise(run() as Promise)
}
// use in DEBUG or wherever you can't receive push notifications to poll for messages.
@ -176,25 +176,25 @@ public class MessageFetcherJob: NSObject {
}
private func fetchUndeliveredMessages() -> Promise<(envelopes: [SSKProtoEnvelope], more: Bool)> {
return Promise { fulfill, reject in
return Promise { resolver in
let request = OWSRequestFactory.getMessagesRequest()
self.networkManager.makeRequest(
request,
success: { (_: URLSessionDataTask?, responseObject: Any?) -> Void in
guard let (envelopes, more) = self.parseMessagesResponse(responseObject: responseObject) else {
Logger.error("response object had unexpected content")
return reject(OWSErrorMakeUnableToProcessServerResponseError())
return resolver.reject(OWSErrorMakeUnableToProcessServerResponseError())
}
fulfill((envelopes: envelopes, more: more))
resolver.fulfill((envelopes: envelopes, more: more))
},
failure: { (_: URLSessionDataTask?, error: Error?) in
guard let error = error else {
Logger.error("error was surpringly nil. sheesh rough day.")
return reject(OWSErrorMakeUnableToProcessServerResponseError())
return resolver.reject(OWSErrorMakeUnableToProcessServerResponseError())
}
reject(error)
resolver.reject(error)
})
}
}

View File

@ -68,7 +68,7 @@ import SignalMessaging
},
failure: { error in
Logger.error("failed with error: \(error) retrying in \(retryDelay)s.")
after(interval: retryDelay).then {
after(seconds: retryDelay).done {
self.run(retryDelay: retryDelay * 2)
}.retainUntilComplete()
})

View File

@ -33,14 +33,9 @@ class SyncPushTokensJob: NSObject {
func run() -> Promise<Void> {
Logger.info("Starting.")
let runPromise: Promise<Void> = DispatchQueue.main.promise {
// HACK: no-op dispatch to work around a bug in PromiseKit/Swift which won't compile
// when dispatching complex Promise types. We should eventually be able to delete the
// following two lines, skipping this no-op dispatch.
return
}.then {
let runPromise = firstly {
return self.pushRegistrationManager.requestPushTokens()
}.then { (pushToken: String, voipToken: String) in
}.then { (pushToken: String, voipToken: String) -> Promise<Void> in
Logger.info("finished: requesting push tokens")
var shouldUploadTokens = false
@ -59,18 +54,17 @@ class SyncPushTokensJob: NSObject {
guard shouldUploadTokens else {
Logger.info("No reason to upload pushToken: \(pushToken), voipToken: \(voipToken)")
return Promise(value: ())
return Promise.value(())
}
Logger.warn("uploading tokens to account servers. pushToken: \(pushToken), voipToken: \(voipToken)")
return self.accountManager.updatePushTokens(pushToken: pushToken, voipToken: voipToken).then {
Logger.info("successfully updated push tokens on server")
return self.recordPushTokensLocally(pushToken: pushToken, voipToken: voipToken)
return firstly {
self.accountManager.updatePushTokens(pushToken: pushToken, voipToken: voipToken)
}.done { _ in
self.recordPushTokensLocally(pushToken: pushToken, voipToken: voipToken)
}
}.then {
}.done {
Logger.info("completed successfully.")
}.catch { error in
Logger.error("Failed with error: \(error).")
}
runPromise.retainUntilComplete()
@ -80,17 +74,21 @@ class SyncPushTokensJob: NSObject {
// MARK: - objc wrappers, since objc can't use swift parameterized types
@objc class func run(accountManager: AccountManager, preferences: OWSPreferences) -> AnyPromise {
@objc
class func run(accountManager: AccountManager, preferences: OWSPreferences) -> AnyPromise {
let promise: Promise<Void> = self.run(accountManager: accountManager, preferences: preferences)
return AnyPromise(promise)
}
@objc func run() -> AnyPromise {
@objc
func run() -> AnyPromise {
let promise: Promise<Void> = self.run()
return AnyPromise(promise)
}
private func recordPushTokensLocally(pushToken: String, voipToken: String) -> Promise<Void> {
// MARK:
private func recordPushTokensLocally(pushToken: String, voipToken: String) {
Logger.warn("Recording push tokens locally. pushToken: \(pushToken), voipToken: \(voipToken)")
var didTokensChange = false
@ -110,7 +108,5 @@ class SyncPushTokensJob: NSObject {
if (didTokensChange) {
NotificationCenter.default.postNotificationNameAsync(SyncPushTokensJob.PushTokensDidChange, object: nil)
}
return Promise(value: ())
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -12,8 +12,8 @@ public extension MessageSender {
* Wrap message sending in a Promise for easier callback chaining.
*/
public func sendPromise(message: TSOutgoingMessage) -> Promise<Void> {
let promise: Promise<Void> = Promise { fulfill, reject in
self.enqueue(message, success: fulfill, failure: reject)
let promise: Promise<Void> = Promise { resolver in
self.enqueue(message, success: resolver.fulfill, failure: resolver.reject)
}
// Ensure sends complete before they're GC'd.

View File

@ -43,7 +43,7 @@ public class AccountManager: NSObject {
@objc func register(verificationCode: String,
pin: String?) -> AnyPromise {
return AnyPromise(register(verificationCode: verificationCode, pin: pin))
return AnyPromise(register(verificationCode: verificationCode, pin: pin) as Promise<Void>)
}
func register(verificationCode: String,
@ -71,7 +71,7 @@ public class AccountManager: NSObject {
default:
throw error
}
}.then {
}.done {
self.completeRegistration()
}
@ -82,11 +82,11 @@ public class AccountManager: NSObject {
private func registerForTextSecure(verificationCode: String,
pin: String?) -> Promise<Void> {
return Promise { fulfill, reject in
return Promise { resolver in
tsAccountManager.verifyAccount(withCode: verificationCode,
pin: pin,
success: fulfill,
failure: reject)
success: resolver.fulfill,
failure: resolver.reject)
}
}
@ -105,38 +105,39 @@ public class AccountManager: NSObject {
// MARK: Message Delivery
func updatePushTokens(pushToken: String, voipToken: String) -> Promise<Void> {
return Promise { fulfill, reject in
return Promise { resolver in
tsAccountManager.registerForPushNotifications(pushToken: pushToken,
voipToken: voipToken,
success: fulfill,
failure: reject)
success: resolver.fulfill,
failure: resolver.reject)
}
}
func enableManualMessageFetching() -> Promise<Void> {
return tsAccountManager.setIsManualMessageFetchEnabled(true).asPromise().asVoid()
let anyPromise = tsAccountManager.setIsManualMessageFetchEnabled(true)
return Promise(anyPromise).asVoid()
}
// MARK: Turn Server
func getTurnServerInfo() -> Promise<TurnServerInfo> {
return Promise { fulfill, reject in
return Promise { resolver in
self.networkManager.makeRequest(OWSRequestFactory.turnServerInfoRequest(),
success: { (_: URLSessionDataTask, responseObject: Any?) in
guard responseObject != nil else {
return reject(OWSErrorMakeUnableToProcessServerResponseError())
return resolver.reject(OWSErrorMakeUnableToProcessServerResponseError())
}
if let responseDictionary = responseObject as? [String: AnyObject] {
if let turnServerInfo = TurnServerInfo(attributes: responseDictionary) {
return fulfill(turnServerInfo)
return resolver.fulfill(turnServerInfo)
}
Logger.error("unexpected server response:\(responseDictionary)")
}
return reject(OWSErrorMakeUnableToProcessServerResponseError())
return resolver.reject(OWSErrorMakeUnableToProcessServerResponseError())
},
failure: { (_: URLSessionDataTask, error: Error) in
return reject(error)
return resolver.reject(error)
})
}
}

View File

@ -41,11 +41,11 @@ class DebugUICalling: DebugUIPage {
}
let callMessage = OWSOutgoingCallMessage(thread: thread, hangupMessage: hangupMessage)
strongSelf.messageSender.sendPromise(message: callMessage).then {
strongSelf.messageSender.sendPromise(message: callMessage).done {
Logger.debug("Successfully sent hangup call message to \(thread.contactIdentifier())")
}.catch { error in
Logger.error("failed to send hangup call message to \(thread.contactIdentifier()) with error: \(error)")
}
}.retainUntilComplete()
},
OWSTableItem(title: "Send 'busy' for old call") { [weak self] in
guard let strongSelf = self else { return }
@ -62,11 +62,11 @@ class DebugUICalling: DebugUIPage {
let callMessage = OWSOutgoingCallMessage(thread: thread, busyMessage: busyMessage)
strongSelf.messageSender.sendPromise(message: callMessage).then {
strongSelf.messageSender.sendPromise(message: callMessage).done {
Logger.debug("Successfully sent busy call message to \(thread.contactIdentifier())")
}.catch { error in
Logger.error("failed to send busy call message to \(thread.contactIdentifier()) with error: \(error)")
}
}.retainUntilComplete()
}
]

View File

@ -42,11 +42,11 @@ class DebugUIProfile: DebugUIPage {
guard let strongSelf = self else { return }
let message = OWSProfileKeyMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: aThread)
strongSelf.messageSender.sendPromise(message: message).then {
strongSelf.messageSender.sendPromise(message: message).done {
Logger.info("Successfully sent profile key message to thread: \(String(describing: aThread))")
}.catch { _ in
owsFailDebug("Failed to send profile key message to thread: \(String(describing: aThread))")
}
}.catch { _ in
owsFailDebug("Failed to send profile key message to thread: \(String(describing: aThread))")
}.retainUntilComplete()
}
]

View File

@ -249,7 +249,7 @@ class GifPickerCell: UICollectionViewCell {
return Promise(error: GiphyError.assertionError(description: "renditionForSending was unexpectedly nil"))
}
let (promise, fulfill, reject) = Promise<GiphyAsset>.pending()
let (promise, resolver) = Promise<GiphyAsset>.pending()
// We don't retain a handle on the asset request, since there will only ever
// be one selected asset, and we never want to cancel it.
@ -257,13 +257,13 @@ class GifPickerCell: UICollectionViewCell {
.sharedInstance.requestAsset(rendition: renditionForSending,
priority: .high,
success: { _, asset in
fulfill(asset)
resolver.fulfill(asset)
},
failure: { _ in
// TODO GiphyDownloader API should pass through a useful failing error
// so we can pass it through here
Logger.error("request failed")
reject(GiphyError.fetchFailure)
resolver.reject(GiphyError.fetchFailure)
})
return promise

View File

@ -6,6 +6,7 @@ import Foundation
import SignalServiceKit
import Reachability
import SignalMessaging
import PromiseKit
@objc
protocol GifPickerViewControllerDelegate: class {
@ -361,7 +362,9 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
public func getFileForCell(_ cell: GifPickerCell) {
GiphyDownloader.sharedInstance.cancelAllRequests()
cell.requestRenditionForSending().then { [weak self] (asset: GiphyAsset) -> Void in
firstly {
cell.requestRenditionForSending()
}.done { [weak self] (asset: GiphyAsset) in
guard let strongSelf = self else {
Logger.info("ignoring send, since VC was dismissed before fetching finished.")
return
@ -397,7 +400,6 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
strongSelf.present(alert, animated: true, completion: nil)
}.retainUntilComplete()
}
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {

View File

@ -939,7 +939,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"beggining refreshing.");
[SignalApp.sharedApp.messageFetcherJob run].always(^{
[SignalApp.sharedApp.messageFetcherJob run].ensure(^{
OWSLogInfo(@"ending refreshing.");
[refreshControl endRefreshing];
});

View File

@ -442,7 +442,7 @@ protocol CallAudioServiceDelegate: class {
// Since a call notification is more urgent than a message notifaction, we
// vibrate twice, like a pulse, to differentiate from a normal notification vibration.
vibrate()
DispatchQueue.default.asyncAfter(deadline: DispatchTime.now() + pulseDuration) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + pulseDuration) {
self.vibrate()
}
}

View File

@ -104,19 +104,16 @@ private class SignalCallData: NSObject {
public let call: SignalCall
// Used to coordinate promises across delegate methods
let fulfillCallConnectedPromise: (() -> Void)
let rejectCallConnectedPromise: ((Error) -> Void)
let callConnectedPromise: Promise<Void>
let callConnectedResolver: Resolver<Void>
// Used to ensure any received ICE messages wait until the peer connection client is set up.
let fulfillPeerConnectionClientPromise: (() -> Void)
let rejectPeerConnectionClientPromise: ((Error) -> Void)
let peerConnectionClientPromise: Promise<Void>
let peerConnectionClientResolver: Resolver<Void>
// Used to ensure CallOffer was sent before sending any ICE updates.
let fulfillReadyToSendIceUpdatesPromise: (() -> Void)
let rejectReadyToSendIceUpdatesPromise: ((Error) -> Void)
let readyToSendIceUpdatesPromise: Promise<Void>
let readyToSendIceUpdatesResolver: Resolver<Void>
weak var localCaptureSession: AVCaptureSession? {
didSet {
@ -153,26 +150,17 @@ private class SignalCallData: NSObject {
required init(call: SignalCall) {
self.call = call
let (callConnectedPromise, fulfillCallConnectedPromise, rejectCallConnectedPromise) = Promise<Void>.pending()
let (callConnectedPromise, callConnectedResolver) = Promise<Void>.pending()
self.callConnectedPromise = callConnectedPromise
self.fulfillCallConnectedPromise = {
fulfillCallConnectedPromise(())
}
self.rejectCallConnectedPromise = rejectCallConnectedPromise
self.callConnectedResolver = callConnectedResolver
let (peerConnectionClientPromise, fulfillPeerConnectionClientPromise, rejectPeerConnectionClientPromise) = Promise<Void>.pending()
let (peerConnectionClientPromise, peerConnectionClientResolver) = Promise<Void>.pending()
self.peerConnectionClientPromise = peerConnectionClientPromise
self.fulfillPeerConnectionClientPromise = {
fulfillPeerConnectionClientPromise(())
}
self.rejectPeerConnectionClientPromise = rejectPeerConnectionClientPromise
self.peerConnectionClientResolver = peerConnectionClientResolver
let (readyToSendIceUpdatesPromise, fulfillReadyToSendIceUpdatesPromise, rejectReadyToSendIceUpdatesPromise) = Promise<Void>.pending()
let (readyToSendIceUpdatesPromise, readyToSendIceUpdatesResolver) = Promise<Void>.pending()
self.readyToSendIceUpdatesPromise = readyToSendIceUpdatesPromise
self.fulfillReadyToSendIceUpdatesPromise = {
fulfillReadyToSendIceUpdatesPromise(())
}
self.rejectReadyToSendIceUpdatesPromise = rejectReadyToSendIceUpdatesPromise
self.readyToSendIceUpdatesResolver = readyToSendIceUpdatesResolver
super.init()
}
@ -192,15 +180,15 @@ private class SignalCallData: NSObject {
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
// There is no harm in rejecting a previously fulfilled promise.
rejectCallConnectedPromise(CallError.obsoleteCall(description: "Terminating call"))
self.callConnectedResolver.reject(CallError.obsoleteCall(description: "Terminating call"))
// In case we're still waiting on the peer connection setup somewhere, we need to reject it to avoid a memory leak.
// There is no harm in rejecting a previously fulfilled promise.
rejectPeerConnectionClientPromise(CallError.obsoleteCall(description: "Terminating call"))
self.peerConnectionClientResolver.reject(CallError.obsoleteCall(description: "Terminating call"))
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
// There is no harm in rejecting a previously fulfilled promise.
rejectReadyToSendIceUpdatesPromise(CallError.obsoleteCall(description: "Terminating call"))
self.readyToSendIceUpdatesResolver.reject(CallError.obsoleteCall(description: "Terminating call"))
peerConnectionClient?.terminate()
Logger.debug("setting peerConnectionClient")
@ -395,7 +383,7 @@ private class SignalCallData: NSObject {
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .outgoing, useTurnOnly: useTurnOnly)
Logger.debug("setting peerConnectionClient for call: \(call.identifiersForLogs)")
callData.peerConnectionClient = peerConnectionClient
callData.fulfillPeerConnectionClientPromise()
callData.peerConnectionClientResolver.fulfill(())
return peerConnectionClient.createOffer()
}.then { (sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> in
@ -409,10 +397,12 @@ private class SignalCallData: NSObject {
Logger.info("session description for outgoing call: \(call.identifiersForLogs), sdp: \(sessionDescription.logSafeDescription).")
return peerConnectionClient.setLocalSessionDescription(sessionDescription).then {
return firstly {
peerConnectionClient.setLocalSessionDescription(sessionDescription)
}.then { _ -> Promise<Void> in
do {
let offerBuilder = SSKProtoCallMessageOffer.builder(id: call.signalingId,
sessionDescription: sessionDescription.sdp)
sessionDescription: sessionDescription.sdp)
let callMessage = OWSOutgoingCallMessage(thread: call.thread, offerMessage: try offerBuilder.build())
return self.messageSender.sendPromise(message: callMessage)
} catch {
@ -420,7 +410,7 @@ private class SignalCallData: NSObject {
throw CallError.fatalError(description: "Couldn't build proto")
}
}
}.then {
}.then { () -> Promise<Void> in
guard self.call == call else {
throw CallError.obsoleteCall(description: "obsolete call")
}
@ -430,7 +420,7 @@ private class SignalCallData: NSObject {
self.readyToSendIceUpdates(call: call)
// Don't let the outgoing call ring forever. We don't support inbound ringing forever anyway.
let timeout: Promise<Void> = after(interval: connectingTimeoutSeconds).then { () -> Void in
let timeout: Promise<Void> = after(seconds: connectingTimeoutSeconds).done {
// This code will always be called, whether or not the call has timed out.
// However, if the call has already connected, the `race` promise will have already been
// fulfilled. Rejecting an already fulfilled promise is a no-op.
@ -438,11 +428,13 @@ private class SignalCallData: NSObject {
}
return race(timeout, callData.callConnectedPromise)
}.then {
}.done {
Logger.info(self.call == call
? "outgoing call connected: \(call.identifiersForLogs)."
: "obsolete outgoing call connected: \(call.identifiersForLogs).")
}.catch { error in
}
promise.catch { error in
Logger.error("placing call \(call.identifiersForLogs) failed with error: \(error)")
if let callError = error as? CallError {
@ -456,8 +448,8 @@ private class SignalCallData: NSObject {
let externalError = CallError.externalError(underlyingError: error)
self.handleFailedCall(failedCall: call, error: externalError)
}
}
promise.retainUntilComplete()
}.retainUntilComplete()
return promise
}
@ -473,7 +465,7 @@ private class SignalCallData: NSObject {
return
}
callData.fulfillReadyToSendIceUpdatesPromise()
callData.readyToSendIceUpdatesResolver.fulfill(())
}
/**
@ -500,7 +492,10 @@ private class SignalCallData: NSObject {
}
let sessionDescription = RTCSessionDescription(type: .answer, sdp: sessionDescription)
let setDescriptionPromise = peerConnectionClient.setRemoteSessionDescription(sessionDescription).then {
firstly {
peerConnectionClient.setRemoteSessionDescription(sessionDescription)
}.done {
Logger.debug("successfully set remote description")
}.catch { error in
if let callError = error as? CallError {
@ -511,8 +506,7 @@ private class SignalCallData: NSObject {
let externalError = CallError.externalError(underlyingError: error)
self.handleFailedCall(failedCall: call, error: externalError)
}
}
setDescriptionPromise.retainUntilComplete()
}.retainUntilComplete()
}
/**
@ -683,8 +677,8 @@ private class SignalCallData: NSObject {
strongSelf.handleFailedCall(failedCall: newCall, error: timeout)
})
let incomingCallPromise = firstly {
return getIceServers()
firstly {
getIceServers()
}.then { (iceServers: [RTCIceServer]) -> Promise<HardenedRTCSessionDescription> in
// FIXME for first time call recipients I think we'll see mic/camera permission requests here,
// even though, from the users perspective, no incoming call is yet visible.
@ -702,14 +696,14 @@ private class SignalCallData: NSObject {
Logger.debug("setting peerConnectionClient for: \(newCall.identifiersForLogs)")
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .incoming, useTurnOnly: useTurnOnly)
callData.peerConnectionClient = peerConnectionClient
callData.fulfillPeerConnectionClientPromise()
callData.peerConnectionClientResolver.fulfill(())
let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription)
let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
// Find a sessionDescription compatible with my constraints and the remote sessionDescription
return peerConnectionClient.negotiateSessionDescription(remoteDescription: offerSessionDescription, constraints: constraints)
}.then { (negotiatedSessionDescription: HardenedRTCSessionDescription) in
}.then { (negotiatedSessionDescription: HardenedRTCSessionDescription) -> Promise<Void> in
guard self.call == newCall else {
throw CallError.obsoleteCall(description: "negotiateSessionDescription() response for obsolete call")
}
@ -726,7 +720,7 @@ private class SignalCallData: NSObject {
owsFailDebug("Couldn't build proto")
throw CallError.fatalError(description: "Couldn't build proto")
}
}.then {
}.then { () -> Promise<Void> in
guard self.call == newCall else {
throw CallError.obsoleteCall(description: "sendPromise(message: ) response for obsolete call")
}
@ -736,7 +730,7 @@ private class SignalCallData: NSObject {
// a more intuitive ordering.
self.readyToSendIceUpdates(call: newCall)
let timeout: Promise<Void> = after(interval: connectingTimeoutSeconds).then { () -> Void in
let timeout: Promise<Void> = after(seconds: connectingTimeoutSeconds).done {
// rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled
OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingIncoming(), file: #file, function: #function, line: #line)
throw CallError.timeout(description: "timed out waiting for call to connect")
@ -744,11 +738,11 @@ private class SignalCallData: NSObject {
// This will be fulfilled (potentially) by the RTCDataChannel delegate method
return race(callData.callConnectedPromise, timeout)
}.then {
}.done {
Logger.info(self.call == newCall
? "incoming call connected: \(newCall.identifiersForLogs)."
: "obsolete incoming call connected: \(newCall.identifiersForLogs).")
}.catch { error in
}.recover { error in
guard self.call == newCall else {
Logger.debug("ignoring error: \(error) for obsolete call: \(newCall.identifiersForLogs).")
return
@ -761,13 +755,12 @@ private class SignalCallData: NSObject {
let externalError = CallError.externalError(underlyingError: error)
self.handleFailedCall(failedCall: newCall, error: externalError)
}
}.always {
}.ensure {
Logger.debug("ending background task awaiting inbound call connection")
assert(backgroundTask != nil)
backgroundTask = nil
}
incomingCallPromise.retainUntilComplete()
}.retainUntilComplete()
}
/**
@ -782,7 +775,7 @@ private class SignalCallData: NSObject {
return
}
callData.peerConnectionClientPromise.then { () -> Void in
callData.peerConnectionClientPromise.done {
AssertIsOnMainThread()
guard let call = self.call else {
@ -829,7 +822,7 @@ private class SignalCallData: NSObject {
// Wait until we've sent the CallOffer before sending any ice updates for the call to ensure
// intuitive message ordering for other clients.
callData.readyToSendIceUpdatesPromise.then { () -> Void in
callData.readyToSendIceUpdatesPromise.done {
guard call == self.call else {
self.handleFailedCurrentCall(error: .obsoleteCall(description: "current call changed since we became ready to send ice updates"))
return
@ -1072,7 +1065,7 @@ private class SignalCallData: NSObject {
Logger.info("handleConnectedCall: \(callData.call.identifiersForLogs).")
// cancel connection timeout
callData.fulfillCallConnectedPromise()
callData.callConnectedResolver.fulfill(())
callData.call.state = .connected
@ -1197,13 +1190,14 @@ private class SignalCallData: NSObject {
do {
let hangupBuilder = SSKProtoCallMessageHangup.builder(id: call.signalingId)
let callMessage = OWSOutgoingCallMessage(thread: call.thread, hangupMessage: try hangupBuilder.build())
let sendPromise = self.messageSender.sendPromise(message: callMessage).then {
firstly {
self.messageSender.sendPromise(message: callMessage)
}.done {
Logger.debug("successfully sent hangup call message to \(call.thread.contactIdentifier())")
}.catch { error in
OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalHungupCall(), file: #file, function: #function, line: #line)
Logger.error("failed to send hangup call message to \(call.thread.contactIdentifier()) with error: \(error)")
}
sendPromise.retainUntilComplete()
}.catch { error in
OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalHungupCall(), file: #file, function: #function, line: #line)
Logger.error("failed to send hangup call message to \(call.thread.contactIdentifier()) with error: \(error)")
}.retainUntilComplete()
terminateCall()
} catch {
@ -1518,8 +1512,8 @@ private class SignalCallData: NSObject {
AssertIsOnMainThread()
return firstly {
return accountManager.getTurnServerInfo()
}.then { turnServerInfo -> [RTCIceServer] in
accountManager.getTurnServerInfo()
}.map { turnServerInfo -> [RTCIceServer] in
Logger.debug("got turn server urls: \(turnServerInfo.urls)")
return turnServerInfo.urls.map { url in
@ -1532,11 +1526,11 @@ private class SignalCallData: NSObject {
return RTCIceServer(urlStrings: [url])
}
} + [CallService.fallbackIceServer]
}.recover { error -> [RTCIceServer] in
}.recover { (error: Error) -> Guarantee<[RTCIceServer]> in
Logger.error("fetching ICE servers failed with error: \(error)")
Logger.warn("using fallback ICE Servers")
return [CallService.fallbackIceServer]
return Guarantee.value([CallService.fallbackIceServer])
}
}

View File

@ -34,11 +34,7 @@ class NonCallKitCallUIAdaptee: NSObject, CallUIAdaptee {
// make sure we don't terminate audio session during call
OWSAudioSession.shared.startAudioActivity(call.audioActivity)
self.callService.handleOutgoingCall(call).then {
Logger.debug("handleOutgoingCall succeeded")
}.catch { error in
Logger.error("handleOutgoingCall failed with error: \(error)")
}.retainUntilComplete()
self.callService.handleOutgoingCall(call).retainUntilComplete()
return call
}

View File

@ -498,42 +498,42 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
public func createOffer() -> Promise<HardenedRTCSessionDescription> {
AssertIsOnMainThread()
let proxyCopy = self.proxy
let (promise, fulfill, reject) = Promise<HardenedRTCSessionDescription>.pending()
let (promise, resolver) = Promise<HardenedRTCSessionDescription>.pending()
let completion: ((RTCSessionDescription?, Error?) -> Void) = { (sdp, error) in
guard let strongSelf = proxyCopy.get() else {
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
strongSelf.assertOnSignalingQueue()
guard strongSelf.peerConnection != nil else {
Logger.debug("Ignoring obsolete event in terminated client")
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
if let error = error {
reject(error)
resolver.reject(error)
return
}
guard let sessionDescription = sdp else {
Logger.error("No session description was obtained, even though there was no error reported.")
let error = OWSErrorMakeUnableToProcessServerResponseError()
reject(error)
resolver.reject(error)
return
}
fulfill(HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription))
resolver.fulfill(HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription))
}
PeerConnectionClient.signalingQueue.async {
guard let strongSelf = proxyCopy.get() else {
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
strongSelf.assertOnSignalingQueue()
guard let peerConnection = strongSelf.peerConnection else {
Logger.debug("Ignoring obsolete event in terminated client")
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
@ -549,26 +549,26 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
public func setLocalSessionDescriptionInternal(_ sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> {
let proxyCopy = self.proxy
let (promise, fulfill, reject) = Promise<Void>.pending()
let (promise, resolver) = Promise<Void>.pending()
PeerConnectionClient.signalingQueue.async {
guard let strongSelf = proxyCopy.get() else {
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
strongSelf.assertOnSignalingQueue()
guard let peerConnection = strongSelf.peerConnection else {
Logger.debug("Ignoring obsolete event in terminated client")
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
Logger.verbose("setting local session description: \(sessionDescription)")
peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription, completionHandler: { (error) in
if let error = error {
reject(error)
resolver.reject(error)
} else {
fulfill(())
resolver.fulfill(())
}
})
}
@ -578,16 +578,16 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
public func setLocalSessionDescription(_ sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> {
AssertIsOnMainThread()
let proxyCopy = self.proxy
let (promise, fulfill, reject) = Promise<Void>.pending()
let (promise, resolver) = Promise<Void>.pending()
PeerConnectionClient.signalingQueue.async {
guard let strongSelf = proxyCopy.get() else {
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
strongSelf.assertOnSignalingQueue()
guard let peerConnection = strongSelf.peerConnection else {
Logger.debug("Ignoring obsolete event in terminated client")
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
@ -595,10 +595,10 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription,
completionHandler: { error in
if let error = error {
reject(error)
resolver.reject(error)
return
}
fulfill(())
resolver.fulfill(())
})
}
@ -609,37 +609,37 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
AssertIsOnMainThread()
let proxyCopy = self.proxy
return setRemoteSessionDescription(remoteDescription)
.then(on: PeerConnectionClient.signalingQueue) {
.then(on: PeerConnectionClient.signalingQueue) { _ -> Promise<HardenedRTCSessionDescription> in
guard let strongSelf = proxyCopy.get() else {
return Promise(error: NSError(domain: "Obsolete client", code: 0, userInfo: nil))
}
return strongSelf.negotiateAnswerSessionDescription(constraints: constraints)
}
return strongSelf.negotiateAnswerSessionDescription(constraints: constraints)
}
}
public func setRemoteSessionDescription(_ sessionDescription: RTCSessionDescription) -> Promise<Void> {
AssertIsOnMainThread()
let proxyCopy = self.proxy
let (promise, fulfill, reject) = Promise<Void>.pending()
let (promise, resolver) = Promise<Void>.pending()
PeerConnectionClient.signalingQueue.async {
guard let strongSelf = proxyCopy.get() else {
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
strongSelf.assertOnSignalingQueue()
guard let peerConnection = strongSelf.peerConnection else {
Logger.debug("Ignoring obsolete event in terminated client")
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
Logger.verbose("setting remote description: \(sessionDescription)")
peerConnection.setRemoteDescription(sessionDescription,
completionHandler: { error in
if let error = error {
reject(error)
resolver.reject(error)
return
}
fulfill(())
resolver.fulfill(())
})
}
return promise
@ -648,50 +648,50 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
private func negotiateAnswerSessionDescription(constraints: RTCMediaConstraints) -> Promise<HardenedRTCSessionDescription> {
assertOnSignalingQueue()
let proxyCopy = self.proxy
let (promise, fulfill, reject) = Promise<HardenedRTCSessionDescription>.pending()
let (promise, resolver) = Promise<HardenedRTCSessionDescription>.pending()
let completion: ((RTCSessionDescription?, Error?) -> Void) = { (sdp, error) in
guard let strongSelf = proxyCopy.get() else {
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
strongSelf.assertOnSignalingQueue()
guard strongSelf.peerConnection != nil else {
Logger.debug("Ignoring obsolete event in terminated client")
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
if let error = error {
reject(error)
resolver.reject(error)
return
}
guard let sessionDescription = sdp else {
Logger.error("unexpected empty session description, even though no error was reported.")
let error = OWSErrorMakeUnableToProcessServerResponseError()
reject(error)
resolver.reject(error)
return
}
let hardenedSessionDescription = HardenedRTCSessionDescription(rtcSessionDescription: sessionDescription)
strongSelf.setLocalSessionDescriptionInternal(hardenedSessionDescription)
.then(on: PeerConnectionClient.signalingQueue) { _ in
fulfill(hardenedSessionDescription)
.done(on: PeerConnectionClient.signalingQueue) {
resolver.fulfill(hardenedSessionDescription)
}.catch { error in
reject(error)
resolver.reject(error)
}
}
PeerConnectionClient.signalingQueue.async {
guard let strongSelf = proxyCopy.get() else {
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}
strongSelf.assertOnSignalingQueue()
guard let peerConnection = strongSelf.peerConnection else {
Logger.debug("Ignoring obsolete event in terminated client")
reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
resolver.reject(NSError(domain: "Obsolete client", code: 0, userInfo: nil))
return
}

View File

@ -275,8 +275,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
// We can't wait for long before fulfilling the CXAction, else CallKit will show a "Failed Call". We don't
// actually need to wait for the outcome of the handleOutgoingCall promise, because it handles any errors by
// manually failing the call.
let callPromise = self.callService.handleOutgoingCall(call)
callPromise.retainUntilComplete()
self.callService.handleOutgoingCall(call).retainUntilComplete()
action.fulfill()
self.provider.reportOutgoingCall(with: call.localId, startedConnectingAt: nil)

View File

@ -36,28 +36,29 @@ public enum PushRegistrationError: Error {
}
private var userNotificationSettingsPromise: Promise<Void>?
private var fulfillUserNotificationSettingsPromise: (() -> Void)?
private var userNotificationSettingsResolver: Resolver<Void>?
private var vanillaTokenPromise: Promise<Data>?
private var fulfillVanillaTokenPromise: ((Data) -> Void)?
private var rejectVanillaTokenPromise: ((Error) -> Void)?
private var vanillaTokenResolver: Resolver<Data>?
private var voipRegistry: PKPushRegistry?
private var voipTokenPromise: Promise<Data>?
private var fulfillVoipTokenPromise: ((Data) -> Void)?
private var voipTokenResolver: Resolver<Data>?
// MARK: Public interface
public func requestPushTokens() -> Promise<(pushToken: String, voipToken: String)> {
Logger.info("")
return self.registerUserNotificationSettings().then {
return firstly {
self.registerUserNotificationSettings()
}.then { () -> Promise<(pushToken: String, voipToken: String)> in
guard !Platform.isSimulator else {
throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")
}
return self.registerForVanillaPushToken().then { vanillaPushToken in
self.registerForVoipPushToken().then { voipPushToken in
return self.registerForVanillaPushToken().then { vanillaPushToken -> Promise<(pushToken: String, voipToken: String)> in
self.registerForVoipPushToken().map { voipPushToken in
(pushToken: vanillaPushToken, voipToken: voipPushToken)
}
}
@ -71,12 +72,12 @@ public enum PushRegistrationError: Error {
// we register user notification settings.
@objc
public func didRegisterUserNotificationSettings() {
guard let fulfillUserNotificationSettingsPromise = self.fulfillUserNotificationSettingsPromise else {
guard let userNotificationSettingsResolver = self.userNotificationSettingsResolver else {
owsFailDebug("promise completion in \(#function) unexpectedly nil")
return
}
fulfillUserNotificationSettingsPromise()
userNotificationSettingsResolver.fulfill(())
}
// MARK: Vanilla push token
@ -84,23 +85,23 @@ public enum PushRegistrationError: Error {
// Vanilla push token is obtained from the system via AppDelegate
@objc
public func didReceiveVanillaPushToken(_ tokenData: Data) {
guard let fulfillVanillaTokenPromise = self.fulfillVanillaTokenPromise else {
guard let vanillaTokenResolver = self.vanillaTokenResolver else {
owsFailDebug("promise completion in \(#function) unexpectedly nil")
return
}
fulfillVanillaTokenPromise(tokenData)
vanillaTokenResolver.fulfill(tokenData)
}
// Vanilla push token is obtained from the system via AppDelegate
@objc
public func didFailToReceiveVanillaPushToken(error: Error) {
guard let rejectVanillaTokenPromise = self.rejectVanillaTokenPromise else {
guard let vanillaTokenResolver = self.vanillaTokenResolver else {
owsFailDebug("promise completion in \(#function) unexpectedly nil")
return
}
rejectVanillaTokenPromise(error)
vanillaTokenResolver.reject(error)
}
// MARK: PKPushRegistryDelegate - voIP Push Token
@ -115,12 +116,12 @@ public enum PushRegistrationError: Error {
Logger.info("")
assert(type == .voIP)
assert(credentials.type == .voIP)
guard let fulfillVoipTokenPromise = self.fulfillVoipTokenPromise else {
guard let voipTokenResolver = self.voipTokenResolver else {
owsFailDebug("fulfillVoipTokenPromise was unexpectedly nil")
return
}
fulfillVoipTokenPromise(credentials.token)
voipTokenResolver.fulfill(credentials.token)
}
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
@ -143,11 +144,9 @@ public enum PushRegistrationError: Error {
return promise
}
let (promise, fulfill, _) = Promise<Void>.pending()
let (promise, resolver) = Promise<Void>.pending()
self.userNotificationSettingsPromise = promise
self.fulfillUserNotificationSettingsPromise = {
fulfill(())
}
self.userNotificationSettingsResolver = resolver
Logger.info("registering user notification settings")
@ -189,18 +188,18 @@ public enum PushRegistrationError: Error {
let promise = vanillaTokenPromise!
assert(promise.isPending)
Logger.info("alreay pending promise for vanilla push token")
return promise.then { $0.hexEncodedString }
return promise.map { $0.hexEncodedString }
}
// No pending vanilla token yet. Create a new promise
let (promise, fulfill, reject) = Promise<Data>.pending()
let (promise, resolver) = Promise<Data>.pending()
self.vanillaTokenPromise = promise
self.fulfillVanillaTokenPromise = fulfill
self.rejectVanillaTokenPromise = reject
self.vanillaTokenResolver = resolver
UIApplication.shared.registerForRemoteNotifications()
let kTimeout: TimeInterval = 10
let timeout: Promise<Data> = after(seconds: kTimeout).then { throw PushRegistrationError.timeout }
let timeout: Promise<Data> = after(seconds: kTimeout).map { throw PushRegistrationError.timeout }
let promiseWithTimeout: Promise<Data> = race(promise, timeout)
return promiseWithTimeout.recover { error -> Promise<Data> in
@ -219,7 +218,7 @@ public enum PushRegistrationError: Error {
default:
throw error
}
}.then { (pushTokenData: Data) -> String in
}.map { (pushTokenData: Data) -> String in
if self.isSusceptibleToFailedPushRegistration {
// Sentinal in case this bug is fixed.
owsFailDebug("Device was unexpectedly able to complete push registration even though it was susceptible to failure.")
@ -227,7 +226,7 @@ public enum PushRegistrationError: Error {
Logger.info("successfully registered for vanilla push notifications")
return pushTokenData.hexEncodedString
}.always {
}.ensure {
self.vanillaTokenPromise = nil
}
}
@ -239,13 +238,13 @@ public enum PushRegistrationError: Error {
guard self.voipTokenPromise == nil else {
let promise = self.voipTokenPromise!
assert(promise.isPending)
return promise.then { $0.hexEncodedString }
return promise.map { $0.hexEncodedString }
}
// No pending voip token yet. Create a new promise
let (promise, fulfill, reject) = Promise<Data>.pending()
let (promise, resolver) = Promise<Data>.pending()
self.voipTokenPromise = promise
self.fulfillVoipTokenPromise = fulfill
self.voipTokenResolver = resolver
if self.voipRegistry == nil {
// We don't create the voip registry in init, because it immediately requests the voip token,
@ -258,8 +257,8 @@ public enum PushRegistrationError: Error {
guard let voipRegistry = self.voipRegistry else {
owsFailDebug("failed to initialize voipRegistry")
reject(PushRegistrationError.assertionError(description: "failed to initialize voipRegistry"))
return promise.then { _ in
resolver.reject(PushRegistrationError.assertionError(description: "failed to initialize voipRegistry"))
return promise.map { _ in
// coerce expected type of returned promise - we don't really care about the value,
// since this promise has been rejected. In practice this shouldn't happen
String()
@ -270,13 +269,13 @@ public enum PushRegistrationError: Error {
// rather than waiting for the delegate method to be called.
if let voipTokenData = voipRegistry.pushToken(for: .voIP) {
Logger.info("using pre-registered voIP token")
fulfill(voipTokenData)
resolver.fulfill(voipTokenData)
}
return promise.then { (voipTokenData: Data) -> String in
return promise.map { (voipTokenData: Data) -> String in
Logger.info("successfully registered for voip push notifications")
return voipTokenData.hexEncodedString
}.always {
}.ensure {
self.voipTokenPromise = nil
}
}

View File

@ -37,7 +37,7 @@ class AppUpdateNag: NSObject {
firstly {
self.versionService.fetchLatestVersion(lookupURL: lookupURL)
}.then { appStoreRecord -> Void in
}.done { appStoreRecord in
guard appStoreRecord.version.compare(currentVersion, options: .numeric) == ComparisonResult.orderedDescending else {
Logger.debug("remote version: \(appStoreRecord) is not newer than currentVersion: \(currentVersion)")
return
@ -201,12 +201,12 @@ class AppStoreVersionService: NSObject {
func fetchLatestVersion(lookupURL: URL) -> Promise<AppStoreRecord> {
Logger.debug("lookupURL:\(lookupURL)")
let (promise, fulfill, reject) = Promise<AppStoreRecord>.pending()
let (promise, resolver) = Promise<AppStoreRecord>.pending()
let task = URLSession.ephemeral.dataTask(with: lookupURL) { (data, _, error) in
guard let data = data else {
Logger.warn("data was unexpectedly nil")
reject(OWSErrorMakeUnableToProcessServerResponseError())
resolver.reject(OWSErrorMakeUnableToProcessServerResponseError())
return
}
@ -215,13 +215,13 @@ class AppStoreVersionService: NSObject {
let resultSet = try decoder.decode(AppStoreLookupResultSet.self, from: data)
guard let appStoreRecord = resultSet.results.first else {
Logger.warn("record was unexpectedly nil")
reject(OWSErrorMakeUnableToProcessServerResponseError())
resolver.reject(OWSErrorMakeUnableToProcessServerResponseError())
return
}
fulfill(appStoreRecord)
resolver.fulfill(appStoreRecord)
} catch {
reject(error)
resolver.reject(error)
}
}

View File

@ -68,7 +68,7 @@ class AccountManagerTest: SignalBaseTest {
firstly {
accountManager.register(verificationCode: "", pin: "")
}.then {
}.done {
XCTFail("Should fail")
}.catch { error in
let nserror = error as NSError
@ -77,7 +77,7 @@ class AccountManagerTest: SignalBaseTest {
} else {
XCTFail("Unexpected error: \(error)")
}
}
}.retainUntilComplete()
self.waitForExpectations(timeout: 1.0, handler: nil)
}
@ -89,7 +89,7 @@ class AccountManagerTest: SignalBaseTest {
firstly {
accountManager.register(verificationCode: "123456", pin: "")
}.then {
}.done {
XCTFail("Should fail")
}.catch { error in
if error is VerificationFailedError {
@ -97,7 +97,7 @@ class AccountManagerTest: SignalBaseTest {
} else {
XCTFail("Unexpected error: \(error)")
}
}
}.retainUntilComplete()
self.waitForExpectations(timeout: 1.0, handler: nil)
}
@ -111,11 +111,11 @@ class AccountManagerTest: SignalBaseTest {
firstly {
accountManager.register(verificationCode: "123456", pin: "")
}.then {
}.done {
expectation.fulfill()
}.catch { error in
XCTFail("Unexpected error: \(error)")
}
}.retainUntilComplete()
self.waitForExpectations(timeout: 1.0, handler: nil)
}
@ -125,11 +125,13 @@ class AccountManagerTest: SignalBaseTest {
let expectation = self.expectation(description: "should fail")
accountManager.updatePushTokens(pushToken: PushNotificationRequestResult.FailTSOnly.rawValue, voipToken: "whatever").then {
firstly {
accountManager.updatePushTokens(pushToken: PushNotificationRequestResult.FailTSOnly.rawValue, voipToken: "whatever")
}.done {
XCTFail("Expected to fail.")
}.catch { _ in
expectation.fulfill()
}
}.retainUntilComplete()
self.waitForExpectations(timeout: 1.0, handler: nil)
}

View File

@ -954,7 +954,7 @@ public class SignalAttachment: NSObject {
guard let url = dataSource.dataUrl() else {
let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI)
attachment.error = .missingData
return (Promise(value: attachment), nil)
return (Promise.value(attachment), nil)
}
let asset = AVAsset(url: url)
@ -962,7 +962,7 @@ public class SignalAttachment: NSObject {
guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetMediumQuality) else {
let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI)
attachment.error = .couldNotConvertToMpeg4
return (Promise(value: attachment), nil)
return (Promise.value(attachment), nil)
}
exportSession.shouldOptimizeForNetworkUse = true
@ -972,7 +972,7 @@ public class SignalAttachment: NSObject {
let exportURL = videoTempPath.appendingPathComponent(UUID().uuidString).appendingPathExtension("mp4")
exportSession.outputURL = exportURL
let (promise, fulfill, _) = Promise<SignalAttachment>.pending()
let (promise, resolver) = Promise<SignalAttachment>.pending()
Logger.debug("starting video export")
exportSession.exportAsynchronously {
@ -985,14 +985,14 @@ public class SignalAttachment: NSObject {
owsFailDebug("Failed to build data source for exported video URL")
let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI)
attachment.error = .couldNotConvertToMpeg4
fulfill(attachment)
resolver.fulfill(attachment)
return
}
dataSource.sourceFilename = mp4Filename
let attachment = SignalAttachment(dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String)
fulfill(attachment)
resolver.fulfill(attachment)
}
return (promise, exportSession)

View File

@ -59,7 +59,7 @@ public class OWS106EnsureProfileComplete: OWSDatabaseMigration {
return
}
self.ensureProfileComplete().then { _ -> Void in
self.ensureProfileComplete().done {
Logger.info("complete. Canceling timer and saving.")
self.completionHandler(true)
}.catch { error in
@ -83,33 +83,33 @@ public class OWS106EnsureProfileComplete: OWSDatabaseMigration {
func ensureProfileComplete() -> Promise<Void> {
guard let localRecipientId = TSAccountManager.localNumber() else {
// local app doesn't think we're registered, so nothing to worry about.
return Promise(value: ())
return Promise.value(())
}
let (promise, fulfill, reject) = Promise<Void>.pending()
ProfileFetcherJob().getProfile(recipientId: localRecipientId).then { _ -> Void in
return firstly {
ProfileFetcherJob().getProfile(recipientId: localRecipientId)
}.done { _ in
Logger.info("verified recipient profile is in good shape: \(localRecipientId)")
fulfill(())
}.catch { error in
}.recover { error -> Promise<Void> in
switch error {
case SignalServiceProfile.ValidationError.invalidIdentityKey(let description):
Logger.warn("detected incomplete profile for \(localRecipientId) error: \(description)")
let (promise, resolver) = Promise<Void>.pending()
// This is the error condition we're looking for. Update prekeys to properly set the identity key, completing registration.
TSPreKeyManager.createPreKeys(success: {
Logger.info("successfully uploaded pre-keys. Profile should be fixed.")
fulfill(())
resolver.fulfill(())
},
failure: { _ in
reject(OWSErrorWithCodeDescription(.signalServiceFailure, "\(self.logTag) Unknown error"))
resolver.reject(OWSErrorWithCodeDescription(.signalServiceFailure, "\(self.logTag) Unknown error"))
})
default:
reject(error)
}
}.retainUntilComplete()
return promise
return promise
default:
throw error
}
}
}
}
}

View File

@ -97,7 +97,7 @@ public class ProfileFetcherJob: NSObject {
}
public func updateProfile(recipientId: String, remainingRetries: Int = 3) {
self.getProfile(recipientId: recipientId).then { profile in
self.getProfile(recipientId: recipientId).map { profile in
self.updateProfile(signalServiceProfile: profile)
}.catch { error in
switch error {
@ -138,20 +138,20 @@ public class ProfileFetcherJob: NSObject {
let socketType: OWSWebSocketType = unidentifiedAccess == nil ? .default : .UD
if socketManager.canMakeRequests(of: socketType) {
let request = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: unidentifiedAccess)
let (promise, fulfill, reject) = Promise<SignalServiceProfile>.pending()
let (promise, resolver) = Promise<SignalServiceProfile>.pending()
self.socketManager.make(request,
webSocketType: socketType,
success: { (responseObject: Any?) -> Void in
do {
let profile = try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject)
fulfill(profile)
resolver.fulfill(profile)
} catch {
reject(error)
resolver.reject(error)
}
},
failure: { (_: NSInteger, _:Data?, error: Error) in
reject(error)
resolver.reject(error)
})
return promise
} else {

View File

@ -53,11 +53,7 @@ An Objective-C library for communicating with the Signal messaging service.
s.dependency 'SignalCoreKit'
s.dependency 'SignalMetadataKit'
# Avoid PromiseKit 5/6 for now.
# From the maintainer:
# > PromiseKit 5 has been released, but is not yet fully documented,
# > so we advise sticking with version 4 for the time being.
s.dependency 'PromiseKit', "~> 4.0"
s.dependency 'PromiseKit', "~> 6.0"
s.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'SignalServiceKit/tests/**/*.{h,m,swift}'

View File

@ -30,20 +30,20 @@ public class CreatePreKeysOperation: OWSOperation {
let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord()
let preKeyRecords: [PreKeyRecord] = self.primaryStorage.generatePreKeyRecords()
firstly {
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
self.primaryStorage.storePreKeyRecords(preKeyRecords)
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
self.primaryStorage.storePreKeyRecords(preKeyRecords)
return self.accountServiceClient.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords)
}.then { () -> Void in
signedPreKeyRecord.markAsAcceptedByService()
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
}.then { () -> Void in
Logger.debug("done")
self.reportSuccess()
}.catch { error in
self.reportError(error)
}.retainUntilComplete()
firstly {
self.accountServiceClient.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords)
}.done {
signedPreKeyRecord.markAsAcceptedByService()
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
Logger.debug("done")
self.reportSuccess()
}.catch { error in
self.reportError(error)
}.retainUntilComplete()
}
}

View File

@ -42,7 +42,7 @@ public class RefreshPreKeysOperation: OWSOperation {
Logger.debug("preKeysCount: \(preKeysCount)")
guard preKeysCount < kEphemeralPreKeysMinimumCount || self.primaryStorage.currentSignedPrekeyId() == nil else {
Logger.debug("Available keys sufficient: \(preKeysCount)")
return Promise(value: ())
return Promise.value(())
}
let identityKey: Data = self.identityKeyManager.identityKeyPair()!.publicKey
@ -52,7 +52,9 @@ public class RefreshPreKeysOperation: OWSOperation {
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
self.primaryStorage.storePreKeyRecords(preKeyRecords)
return self.accountServiceClient.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords).then { () -> Void in
return firstly {
self.accountServiceClient.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords)
}.done {
signedPreKeyRecord.markAsAcceptedByService()
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id)
@ -60,7 +62,7 @@ public class RefreshPreKeysOperation: OWSOperation {
TSPreKeyManager.clearPreKeyUpdateFailureCount()
TSPreKeyManager.clearSignedPreKeyRecords()
}
}.then { () -> Void in
}.done {
Logger.debug("done")
self.reportSuccess()
}.catch { error in

View File

@ -29,10 +29,10 @@ public class RotateSignedPreKeyOperation: OWSOperation {
let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord()
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
firstly {
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
return self.accountServiceClient.setSignedPreKey(signedPreKeyRecord)
}.then(on: DispatchQueue.global()) { () -> Void in
}.done(on: DispatchQueue.global()) {
Logger.info("Successfully uploaded signed PreKey")
signedPreKeyRecord.markAsAcceptedByService()
self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord)
@ -40,7 +40,7 @@ public class RotateSignedPreKeyOperation: OWSOperation {
TSPreKeyManager.clearPreKeyUpdateFailureCount()
TSPreKeyManager.clearSignedPreKeyRecords()
}.then { () -> Void in
Logger.debug("done")
self.reportSuccess()
}.catch { error in

View File

@ -129,7 +129,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
}
AnyPromise *completionPromise = PMKJoin(sendPromises);
completionPromise.always(^() {
completionPromise.ensure(^() {
// Wait N seconds before conducting another pass.
// This allows time for a batch to accumulate.
//

View File

@ -232,39 +232,44 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
@objc
public func ensureSenderCertificateObjC(success:@escaping (SMKSenderCertificate) -> Void,
failure:@escaping (Error) -> Void) {
ensureSenderCertificate()
.then(execute: { certificate in
success(certificate)
})
.catch(execute: { (error) in
failure(error)
}).retainUntilComplete()
firstly {
ensureSenderCertificate()
}.map { certificate in
success(certificate)
}.catch { error in
failure(error)
}.retainUntilComplete()
}
public func ensureSenderCertificate() -> Promise<SMKSenderCertificate> {
// If there is a valid cached sender certificate, use that.
if let certificate = senderCertificate() {
return Promise(value: certificate)
return Promise.value(certificate)
}
// Try to obtain a new sender certificate.
return requestSenderCertificate().then { (certificateData, certificate) in
return firstly {
requestSenderCertificate()
}.map { (certificateData: Data, certificate: SMKSenderCertificate) in
// Cache the current sender certificate.
self.setSenderCertificate(certificateData)
return Promise(value: certificate)
return certificate
}
}
private func requestSenderCertificate() -> Promise<(Data, SMKSenderCertificate)> {
return SignalServiceRestClient().requestUDSenderCertificate().then { (certificateData) -> Promise<(Data, SMKSenderCertificate)> in
private func requestSenderCertificate() -> Promise<(certificateData: Data, certificate: SMKSenderCertificate)> {
return firstly {
SignalServiceRestClient().requestUDSenderCertificate()
}.map { certificateData -> (certificateData: Data, certificate: SMKSenderCertificate) in
let certificate = try SMKSenderCertificate.parse(data: certificateData)
guard self.isValidCertificate(certificate) else {
throw OWSUDError.invalidData(description: "Invalid sender certificate returned by server")
}
return Promise(value: (certificateData, certificate) )
return (certificateData: certificateData, certificate: certificate)
}
}

View File

@ -27,18 +27,19 @@ extension NetworkManagerError {
}
extension TSNetworkManager {
public func makePromise(request: TSRequest) -> Promise<(task: URLSessionDataTask, responseObject: Any?)> {
let (promise, fulfill, reject) = Promise<(task: URLSessionDataTask, responseObject: Any?)>.pending()
let (promise, resolver) = Promise<(task: URLSessionDataTask, responseObject: Any?)>.pending()
self.makeRequest(request,
success: { task, responseObject in
fulfill((task: task, responseObject: responseObject))
resolver.fulfill((task: task, responseObject: responseObject))
},
failure: { task, error in
let nmError = NetworkManagerError.taskError(task: task, underlyingError: error)
let nsError: NSError = nmError as NSError
nsError.isRetryable = (error as NSError).isRetryable
reject(nsError)
resolver.reject(nsError)
})
return promise

View File

@ -38,13 +38,15 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient {
Logger.debug("")
let request = OWSRequestFactory.availablePreKeysCountRequest()
return networkManager.makePromise(request: request).then { (_, responseObject) -> Int in
return firstly {
networkManager.makePromise(request: request)
}.map { _, responseObject in
Logger.debug("got response")
guard let params = ParamParser(responseObject: responseObject) else {
throw self.unexpectedServerResponseError()
}
let count: Int = try! params.required(key: "count")
let count: Int = try params.required(key: "count")
return count
}
@ -66,20 +68,15 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient {
public func requestUDSenderCertificate() -> Promise<Data> {
let request = OWSRequestFactory.udSenderCertificateRequest()
return self.networkManager.makePromise(request: request)
.then(execute: { (_, responseObject) -> Data in
let certificateData = try self.parseUDSenderCertificateResponse(responseObject: responseObject)
return firstly {
self.networkManager.makePromise(request: request)
}.map { _, responseObject in
guard let parser = ParamParser(responseObject: responseObject) else {
throw OWSUDError.invalidData(description: "Invalid sender certificate response")
}
return certificateData
})
}
private func parseUDSenderCertificateResponse(responseObject: Any?) throws -> Data {
guard let parser = ParamParser(responseObject: responseObject) else {
throw OWSUDError.invalidData(description: "Invalid sender certificate response")
return try parser.requiredBase64EncodedData(key: "certificate")
}
return try parser.requiredBase64EncodedData(key: "certificate")
}
@objc
@ -89,19 +86,15 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient {
public func updateAccountAttributes() -> Promise<Void> {
let request = OWSRequestFactory.updateAttributesRequest()
let promise: Promise<Void> = networkManager.makePromise(request: request)
.then(execute: { (_, _) in
Logger.info("updated account attributes on server")
}).catch(execute: { (error) in
Logger.error("failed to update account attributes on server with error: \(error)")
})
return promise
return networkManager.makePromise(request: request).asVoid()
}
public func retrieveProfile(recipientId: RecipientIdentifier, unidentifiedAccess: SSKUnidentifiedAccess?) -> Promise<SignalServiceProfile> {
let request = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: unidentifiedAccess)
return networkManager.makePromise(request: request).then { (_: URLSessionDataTask, responseObject: Any?) in
return try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject)
return firstly {
networkManager.makePromise(request: request)
}.map { _, responseObject in
try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject)
}
}
}

View File

@ -12,10 +12,22 @@ public extension AnyPromise {
*/
@objc
func retainUntilComplete() {
// Unfortunately, there is (currently) no way to surpress the
// compiler warning: "Variable 'retainCycle' was written to, but never read"
var retainCycle: AnyPromise? = self
self.always {
_ = self.ensure {
assert(retainCycle != nil)
retainCycle = nil
}
}
}
public extension PMKFinalizer {
/**
* Sometimes there isn't a straight forward candidate to retain a promise, in that case we tell the
* promise to self retain, until it completes to avoid the risk it's GC'd before completion.
*/
func retainUntilComplete() {
var retainCycle: PMKFinalizer? = self
_ = self.finally {
assert(retainCycle != nil)
retainCycle = nil
}
@ -28,10 +40,22 @@ public extension Promise {
* promise to self retain, until it completes to avoid the risk it's GC'd before completion.
*/
func retainUntilComplete() {
// Unfortunately, there is (currently) no way to surpress the
// compiler warning: "Variable 'retainCycle' was written to, but never read"
var retainCycle: Promise<T>? = self
self.always {
_ = self.ensure {
assert(retainCycle != nil)
retainCycle = nil
}
}
}
public extension Guarantee {
/**
* Sometimes there isn't a straight forward candidate to retain a promise, in that case we tell the
* promise to self retain, until it completes to avoid the risk it's GC'd before completion.
*/
func retainUntilComplete() {
var retainCycle: Guarantee<T>? = self
_ = self.done { _ in
assert(retainCycle != nil)
retainCycle = nil
}

View File

@ -74,7 +74,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
self.loadViewController = loadViewController
// Don't display load screen immediately, in hopes that we can avoid it altogether.
after(seconds: 0.5).then { [weak self] () -> Void in
after(seconds: 0.5).done { [weak self] in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
@ -532,7 +532,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
private func buildAttachmentAndPresentConversationPicker() {
AssertIsOnMainThread()
self.buildAttachment().then { [weak self] attachment -> Void in
self.buildAttachment().map { [weak self] attachment in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
@ -544,7 +544,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
conversationPicker.attachment = attachment
strongSelf.showPrimaryViewController(conversationPicker)
Logger.info("showing picker with attachment: \(attachment)")
}.catch {[weak self] error in
}.catch { [weak self] error in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
@ -690,7 +690,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
}
Logger.debug("matched utiType: \(srcUtiType)")
let (promise, fulfill, reject) = Promise<(itemUrl: URL, utiType: String)>.pending()
let (promise, resolver) = Promise<(itemUrl: URL, utiType: String)>.pending()
var customFileName: String?
var isConvertibleToTextMessage = false
@ -702,13 +702,13 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
guard let strongSelf = self else { return }
guard error == nil else {
reject(error!)
resolver.reject(error!)
return
}
guard let value = value else {
let missingProviderError = ShareViewControllerError.assertionError(description: "missing item provider")
reject(missingProviderError)
resolver.reject(missingProviderError)
return
}
@ -726,7 +726,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
} else {
Logger.error("could not parse vcard.")
let writeError = ShareViewControllerError.assertionError(description: "Could not parse vcard data.")
reject(writeError)
resolver.reject(writeError)
return
}
}
@ -734,21 +734,21 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
let customFileExtension = MIMETypeUtil.fileExtension(forUTIType: srcUtiType)
guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: customFileExtension) else {
let writeError = ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")
reject(writeError)
resolver.reject(writeError)
return
}
let fileUrl = URL(fileURLWithPath: tempFilePath)
fulfill((itemUrl: fileUrl, utiType: srcUtiType))
resolver.fulfill((itemUrl: fileUrl, utiType: srcUtiType))
} else if let string = value as? String {
Logger.debug("string provider: \(string)")
guard let data = string.filterStringForDisplay().data(using: String.Encoding.utf8) else {
let writeError = ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")
reject(writeError)
resolver.reject(writeError)
return
}
guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: "txt") else {
let writeError = ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")
reject(writeError)
resolver.reject(writeError)
return
}
@ -757,18 +757,18 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
isConvertibleToTextMessage = !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String)
if UTTypeConformsTo(srcUtiType as CFString, kUTTypeText) {
fulfill((itemUrl: fileUrl, utiType: srcUtiType))
resolver.fulfill((itemUrl: fileUrl, utiType: srcUtiType))
} else {
fulfill((itemUrl: fileUrl, utiType: kUTTypeText as String))
resolver.fulfill((itemUrl: fileUrl, utiType: kUTTypeText as String))
}
} else if let url = value as? URL {
// If the share itself is a URL (e.g. a link from Safari), try to send this as a text message.
isConvertibleToTextMessage = (itemProvider.registeredTypeIdentifiers.contains(kUTTypeURL as String) &&
!itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String))
if isConvertibleToTextMessage {
fulfill((itemUrl: url, utiType: kUTTypeURL as String))
resolver.fulfill((itemUrl: url, utiType: kUTTypeURL as String))
} else {
fulfill((itemUrl: url, utiType: srcUtiType))
resolver.fulfill((itemUrl: url, utiType: srcUtiType))
}
} else if let image = value as? UIImage {
if let data = UIImagePNGRepresentation(image) {
@ -776,18 +776,18 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
do {
let url = NSURL.fileURL(withPath: tempFilePath)
try data.write(to: url)
fulfill((url, srcUtiType))
resolver.fulfill((url, srcUtiType))
} catch {
reject(ShareViewControllerError.assertionError(description: "couldn't write UIImage: \(String(describing: error))"))
resolver.reject(ShareViewControllerError.assertionError(description: "couldn't write UIImage: \(String(describing: error))"))
}
} else {
reject(ShareViewControllerError.assertionError(description: "couldn't convert UIImage to PNG: \(String(describing: error))"))
resolver.reject(ShareViewControllerError.assertionError(description: "couldn't convert UIImage to PNG: \(String(describing: error))"))
}
} else {
// It's unavoidable that we may sometimes receives data types that we
// don't know how to handle.
let unexpectedTypeError = ShareViewControllerError.assertionError(description: "unexpected value: \(String(describing: value))")
reject(unexpectedTypeError)
resolver.reject(unexpectedTypeError)
}
}
@ -861,7 +861,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
Logger.info("isConvertibleToTextMessage")
attachment.isConvertibleToTextMessage = isConvertibleToTextMessage
}
return Promise(value: attachment)
return Promise.value(attachment)
}
}