element-ios/Riot/Modules/Settings/Discovery/ThreePidDetails/SettingsDiscoveryThreePidDetailsViewModel.swift

285 lines
11 KiB
Swift

// File created from ScreenTemplate
// $ createScreen.sh Details SettingsDiscoveryThreePidDetails
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
enum SettingsDiscoveryThreePidDetailsViewModelError: Error {
case unknown
}
private struct ThreePidRequestTokenInfo {
let clientSecret: String
let sid: String
let bind: Bool
}
final class SettingsDiscoveryThreePidDetailsViewModel: SettingsDiscoveryThreePidDetailsViewModelType {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private var viewState: SettingsDiscoveryThreePidDetailsViewState?
private var currentThreePidRequestTokenInfo: ThreePidRequestTokenInfo?
// MARK: Public
let threePid: MX3PID
weak var viewDelegate: SettingsDiscoveryThreePidDetailsViewModelViewDelegate?
// MARK: - Setup
init(session: MXSession, threePid: MX3PID) {
self.session = session
self.threePid = threePid
}
// MARK: - Public
func process(viewAction: SettingsDiscoveryThreePidDetailsViewAction) {
switch viewAction {
case .load:
self.load()
case .share:
self.share()
case .revoke:
self.revoke()
case .cancelThreePidValidation:
self.cancelThreePidValidation()
case .confirmEmailValidation:
self.confirmEmailValidation()
case .confirmMSISDNValidation(code: let code):
self.validatePhoneNumber(with: code)
}
}
// MARK: - Private
private func load() {
self.update(viewState: .loading)
self.checkThreePidDiscoverability()
}
private func checkThreePidDiscoverability() {
self.isThreePidDiscoverable(self.threePid) { (response) in
switch response {
case .success(let isDiscoverable):
if isDiscoverable {
self.update(viewState: .loaded(displayMode: .revoke))
} else {
self.update(viewState: .loaded(displayMode: .share))
}
case .failure(let error):
self.update(viewState: .error(error))
}
}
}
private func share() {
self.requestToken(for: self.threePid, bind: true)
}
private func revoke() {
self.requestToken(for: self.threePid, bind: false)
}
private func requestToken(for threePid: MX3PID, bind: Bool, useOlderHomeserver: Bool = false) {
guard let restClient = self.session.matrixRestClient,
let clientSecret = MXTools.generateSecret() else {
return
}
let requestThreePidToken: (() -> Void) = {
switch self.threePid.medium {
case .email:
restClient.requestToken(forEmail: threePid.address, isDuringRegistration: false, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid) in
if let sid = sid {
self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind)
self.update(viewState: .loaded(displayMode: .pendingThreePidVerification))
self.registerEmailValidationNotification()
} else {
self.update(viewState: .error(SettingsDiscoveryThreePidDetailsViewModelError.unknown))
}
}, failure: { error in
if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDInUse, useOlderHomeserver == false {
self.requestToken(for: threePid, bind: bind, useOlderHomeserver: true)
} else {
self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown))
}
})
case .msisdn:
let formattedPhoneNumber = self.formattedPhoneNumber(from: threePid.address)
restClient.requestToken(forPhoneNumber: formattedPhoneNumber, isDuringRegistration: false, countryCode: nil, clientSecret: clientSecret, sendAttempt: 1, nextLink: nil, success: { (sid, msisdn) in
if let sid = sid {
self.currentThreePidRequestTokenInfo = ThreePidRequestTokenInfo(clientSecret: clientSecret, sid: sid, bind: bind)
self.update(viewState: .loaded(displayMode: .pendingThreePidVerification))
} else {
self.update(viewState: .error(SettingsDiscoveryThreePidDetailsViewModelError.unknown))
}
}, failure: { error in
if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDInUse, useOlderHomeserver == false {
self.requestToken(for: threePid, bind: bind, useOlderHomeserver: true)
} else {
self.update(viewState: .error(error ?? SettingsDiscoveryThreePidDetailsViewModelError.unknown))
}
})
default:
break
}
}
self.update(viewState: .loading)
if useOlderHomeserver {
restClient.remove3PID(address: threePid.address, medium: threePid.medium.identifier) { (response) in
switch response {
case .success:
requestThreePidToken()
case .failure(let error):
self.update(viewState: .error(error))
}
}
} else {
requestThreePidToken()
}
}
@discardableResult
private func isThreePidDiscoverable(_ threePid: MX3PID, completion: @escaping (_ response: MXResponse<Bool>) -> Void) -> MXHTTPOperation? {
guard let identityService = self.session.identityService else {
completion(.failure(SettingsDiscoveryThreePidDetailsViewModelError.unknown))
return nil
}
return identityService.lookup3PIDs([threePid]) { lookupResponse in
switch lookupResponse {
case .success(let threePids):
completion(.success(threePids.isEmpty == false))
case .failure(let error):
completion(.failure(error))
}
}
}
private func update(viewState: SettingsDiscoveryThreePidDetailsViewState) {
self.viewDelegate?.settingsDiscoveryThreePidDetailsViewModel(self, didUpdateViewState: viewState)
}
private func bindThreePid(_ threePid: MX3PID, threePidRequestTokenInfo: ThreePidRequestTokenInfo) {
guard let restClient = self.session.matrixRestClient else {
return
}
self.update(viewState: .loading)
restClient.addThirdPartyIdentifier(threePidRequestTokenInfo.sid, clientSecret: threePidRequestTokenInfo.clientSecret, bind: threePidRequestTokenInfo.bind) { response in
switch response {
case .success:
if case .email = threePid.medium {
self.unregisterEmailValidationNotification()
}
self.checkThreePidDiscoverability()
case .failure(let error):
if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringThreePIDAuthFailed {
self.update(viewState: .loaded(displayMode: .pendingThreePidVerification))
} else {
if case .email = threePid.medium {
self.unregisterEmailValidationNotification()
}
self.update(viewState: .error(error))
}
}
}
}
// MARK: Email
private func cancelThreePidValidation() {
if case .email = threePid.medium {
self.unregisterEmailValidationNotification()
}
self.currentThreePidRequestTokenInfo = nil
self.checkThreePidDiscoverability()
}
private func confirmEmailValidation() {
guard let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo else {
return
}
self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo)
}
private func registerEmailValidationNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(handleEmailValidationNotification(notification:)), name: .AppDelegateDidValidateEmail, object: nil)
}
private func unregisterEmailValidationNotification() {
NotificationCenter.default.removeObserver(self, name: .AppDelegateDidValidateEmail, object: nil)
}
@objc private func handleEmailValidationNotification(notification: Notification) {
guard let userInfo = notification.userInfo,
let clientSecret = userInfo[AppDelegateDidValidateEmailNotificationClientSecretKey] as? String,
let sid = userInfo[AppDelegateDidValidateEmailNotificationSIDKey] as? String,
let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo,
threePidRequestTokenInfo.clientSecret == clientSecret,
threePidRequestTokenInfo.sid == sid else {
return
}
self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo)
}
// MARK: Phone number
private func formattedPhoneNumber(from phoneNumber: String) -> String {
guard phoneNumber.starts(with: "+") == false else {
return phoneNumber
}
return "+\(threePid.address)"
}
private func validatePhoneNumber(with activationCode: String) {
guard let identityService = self.session.identityService, let threePidRequestTokenInfo = self.currentThreePidRequestTokenInfo else {
return
}
identityService.submit3PIDValidationToken(activationCode, medium: MX3PID.Medium.msisdn.identifier, clientSecret: threePidRequestTokenInfo.clientSecret, sid: threePidRequestTokenInfo.sid) { (response) in
switch response {
case .success:
self.bindThreePid(self.threePid, threePidRequestTokenInfo: threePidRequestTokenInfo)
case .failure(let error):
if let mxError = MXError(nsError: error), mxError.errcode == kMXErrCodeStringUnknownToken {
self.update(viewState: .loaded(displayMode: .pendingThreePidVerification))
} else {
self.update(viewState: .error(error))
}
}
}
}
}