Merge branch 'charlesmchen/onboardingCaptcha'

pull/2/head
Matthew Chen 6 years ago
commit 4d72e0d5d4

@ -77,6 +77,7 @@
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */; };
3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */; };
3448E16422135FFA004B052E /* OnboardingPhoneNumberViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E16322135FFA004B052E /* OnboardingPhoneNumberViewController.swift */; };
3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */; };
344F248D2007CCD600CFB4F4 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */; };
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */; };
3461284B1FD0B94000532771 /* SAELoadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */; };
@ -741,6 +742,7 @@
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingSplashViewController.swift; sourceTree = "<group>"; };
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingBaseViewController.swift; sourceTree = "<group>"; };
3448E16322135FFA004B052E /* OnboardingPhoneNumberViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPhoneNumberViewController.swift; sourceTree = "<group>"; };
3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCaptchaViewController.swift; sourceTree = "<group>"; };
34491FC11FB0F78500B3E5A3 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = translations/my.lproj/Localizable.strings; sourceTree = "<group>"; };
344F248C2007CCD600CFB4F4 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = "<group>"; };
345BC30A2047030600257B7C /* OWS2FASettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS2FASettingsViewController.h; sourceTree = "<group>"; };
@ -1466,6 +1468,7 @@
340FC879204DAC8C007AEB0F /* CodeVerificationViewController.h */,
340FC877204DAC8C007AEB0F /* CodeVerificationViewController.m */,
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */,
3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */,
3448E15D221333F5004B052E /* OnboardingController.swift */,
3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */,
3448E16322135FFA004B052E /* OnboardingPhoneNumberViewController.swift */,
@ -3623,6 +3626,7 @@
340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */,
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */,
340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */,
3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */,
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
340FC8A8204DAC8D007AEB0F /* CodeVerificationViewController.m in Sources */,
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,

@ -1479,7 +1479,7 @@ static NSTimeInterval launchStartedAt;
}
} else {
if (OWSIsDebugBuild()) {
rootViewController = [[OnboardingControllerImpl new] initialViewController];
rootViewController = [[OnboardingController new] initialViewController];
} else {
rootViewController = [RegistrationViewController new];
}

@ -340,8 +340,8 @@ NS_ASSUME_NONNULL_BEGIN
[_requestCodeAgainSpinner startAnimating];
__weak CodeVerificationViewController *weakSelf = self;
[self.tsAccountManager
rerequestSMSWithSuccess:^{
[self.tsAccountManager rerequestSMSWithCaptchaToken:nil
success:^{
OWSLogInfo(@"Successfully requested SMS code");
[weakSelf enableServerActions:YES];
[weakSelf.requestCodeAgainSpinner stopAnimating];
@ -363,8 +363,8 @@ NS_ASSUME_NONNULL_BEGIN
[_requestCallSpinner startAnimating];
__weak CodeVerificationViewController *weakSelf = self;
[self.tsAccountManager
rerequestVoiceWithSuccess:^{
[self.tsAccountManager rerequestVoiceWithCaptchaToken:nil
success:^{
OWSLogInfo(@"Successfully requested voice code");
[weakSelf enableServerActions:YES];

@ -7,6 +7,7 @@ import PromiseKit
@objc
public class OnboardingBaseViewController: OWSViewController {
// Unlike a delegate, we can and should retain a strong reference to the OnboardingController.
let onboardingController: OnboardingController
@ -24,7 +25,7 @@ public class OnboardingBaseViewController: OWSViewController {
notImplemented()
}
// MARK: -
// MARK: - Factory Methods
func titleLabel(text: String) -> UILabel {
let titleLabel = UILabel()
@ -69,7 +70,20 @@ public class OnboardingBaseViewController: OWSViewController {
return button
}
// MARK: Orientation
// MARK: - View Lifecycle
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// TODO: Is there a better way to do this?
if let navigationController = self.navigationController as? OWSNavigationController {
SignalApp.shared().signUpFlowNavigationController = navigationController
} else {
owsFailDebug("Missing or invalid navigationController")
}
}
// MARK: - Orientation
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait

@ -0,0 +1,199 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
import WebKit
@objc
public class OnboardingCaptchaViewController: OnboardingBaseViewController {
private var webView: WKWebView?
override public func loadView() {
super.loadView()
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
// TODO:
// navigationItem.title = NSLocalizedString("SETTINGS_BACKUP", comment: "Label for the backup view in app settings.")
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_CAPTCHA_TITLE", comment: "Title of the 'onboarding Captcha' view."))
let titleRow = UIStackView(arrangedSubviews: [
titleLabel
])
titleRow.axis = .vertical
titleRow.alignment = .fill
titleRow.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 0, right: 16)
titleRow.isLayoutMarginsRelativeArrangement = true
// We want the CAPTCHA web content to "fill the screen (honoring margins)".
// The way to do this with WKWebView is to inject a javascript snippet that
// manipulates the viewport.
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let wkUController = WKUserContentController()
wkUController.addUserScript(userScript)
let wkWebConfig = WKWebViewConfiguration()
wkWebConfig.userContentController = wkUController
let webView = WKWebView(frame: self.view.bounds, configuration: wkWebConfig)
self.webView = webView
webView.navigationDelegate = self
webView.allowsBackForwardNavigationGestures = false
webView.customUserAgent = "Signal iOS (+https://signal.org/download)"
webView.allowsLinkPreview = false
webView.scrollView.contentInset = .zero
webView.layoutMargins = .zero
let stackView = UIStackView(arrangedSubviews: [
titleRow,
webView
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperviewMargins()
stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
NotificationCenter.default.addObserver(self,
selector: #selector(didBecomeActive),
name: NSNotification.Name.OWSApplicationDidBecomeActive,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = false
loadContent()
}
fileprivate let contentUrl = "https://signalcaptchas.org/registration/generate.html"
private func loadContent() {
guard let webView = webView else {
owsFailDebug("Missing webView.")
return
}
guard let url = URL(string: contentUrl) else {
owsFailDebug("Invalid URL.")
return
}
webView.load(URLRequest(url: url))
webView.scrollView.contentOffset = .zero
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.isNavigationBarHidden = false
}
// MARK: - Notifications
@objc func didBecomeActive() {
AssertIsOnMainThread()
loadContent()
}
// MARK: -
private func parseCaptchaAndTryToRegister(url: URL) {
Logger.info("")
guard let captchaToken = parseCaptcha(url: url) else {
owsFailDebug("Could not parse captcha token: \(url)")
// TODO: Alert?
//
// Reload content so user can try again.
loadContent()
return
}
onboardingController.update(captchaToken: captchaToken)
onboardingController.tryToRegister(fromViewController: self, smsVerification: false)
}
private func parseCaptcha(url: URL) -> String? {
Logger.info("")
// Example URL:
// signalcaptcha://03AF6jDqXgf1PocNNrWRJEENZ9l6RAMIsUoESi2dFKkxTgE2qjdZGVjEW6SZNFQqeRRTgGqOii6zHGG--uLyC1HnhSmRt8wHeKxHcg1hsK4ucTusANIeFXVB8wPPiV7U_0w2jUFVak5clMCvW9_JBfbfzj51_e9sou8DYfwc_R6THuTBTdpSV8Nh0yJalgget-nSukCxh6FPA6hRVbw7lP3r-me1QCykHOfh-V29UVaQ4Fs5upHvwB5rtiViqT_HN8WuGmdIdGcaWxaqy1lQTgFSs2Shdj593wZiXfhJnCWAw9rMn3jSgIZhkFxdXwKOmslQ2E_I8iWkm6
guard let host = url.host,
host.count > 0 else {
owsFailDebug("Missing host.")
return nil
}
return host
}
}
// MARK: -
extension OnboardingCaptchaViewController: WKNavigationDelegate {
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
Logger.verbose("navigationAction: \(String(describing: navigationAction.request.url))")
guard let url: URL = navigationAction.request.url else {
owsFailDebug("Missing URL.")
decisionHandler(.cancel)
return
}
if url.scheme == "signalcaptcha" {
decisionHandler(.cancel)
DispatchQueue.main.async {
self.parseCaptchaAndTryToRegister(url: url)
}
return
}
// Loading the Captcha content involves a series of actions.
decisionHandler(.allow)
}
public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
Logger.verbose("navigationResponse: \(String(describing: navigationResponse))")
decisionHandler(.allow)
}
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
Logger.verbose("navigation: \(String(describing: navigation))")
}
public func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
Logger.verbose("navigation: \(String(describing: navigation))")
}
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
Logger.verbose("navigation: \(String(describing: navigation)), error: \(error)")
}
public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
Logger.verbose("navigation: \(String(describing: navigation))")
}
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
Logger.verbose("navigation: \(String(describing: navigation))")
}
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
Logger.verbose("navigation: \(String(describing: navigation)), error: \(error)")
}
public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
Logger.verbose("")
}
}

@ -5,46 +5,285 @@
import UIKit
@objc
public protocol OnboardingController: class {
func initialViewController() -> UIViewController
public class OnboardingCountryState: NSObject {
public let countryName: String
public let callingCode: String
public let countryCode: String
func onboardingSplashDidComplete(viewController: UIViewController)
@objc
public init(countryName: String,
callingCode: String,
countryCode: String) {
self.countryName = countryName
self.callingCode = callingCode
self.countryCode = countryCode
}
public static var defaultValue: OnboardingCountryState {
AssertIsOnMainThread()
var countryCode: String = PhoneNumber.defaultCountryCode()
if let lastRegisteredCountryCode = OnboardingController.lastRegisteredCountryCode(),
lastRegisteredCountryCode.count > 0 {
countryCode = lastRegisteredCountryCode
}
func onboardingPermissionsWasSkipped(viewController: UIViewController)
func onboardingPermissionsDidComplete(viewController: UIViewController)
let callingCodeNumber: NSNumber = PhoneNumberUtil.sharedThreadLocal().nbPhoneNumberUtil.getCountryCode(forRegion: countryCode)
let callingCode = "\(COUNTRY_CODE_PREFIX)\(callingCodeNumber)"
func onboardingPhoneNumberDidComplete(viewController: UIViewController)
var countryName = NSLocalizedString("UNKNOWN_COUNTRY_NAME", comment: "Label for unknown countries.")
if let countryNameDerived = PhoneNumberUtil.countryName(fromCountryCode: countryCode) {
countryName = countryNameDerived
}
return OnboardingCountryState(countryName: countryName, callingCode: callingCode, countryCode: countryCode)
}
}
// MARK: -
@objc
public class OnboardingControllerImpl: NSObject, OnboardingController {
public class OnboardingPhoneNumber: NSObject {
public let e164: String
public let userInput: String
@objc
public init(e164: String,
userInput: String) {
self.e164 = e164
self.userInput = userInput
}
}
// MARK: -
@objc
public class OnboardingController: NSObject {
// MARK: - Dependencies
private var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
// MARK: -
@objc
public override init() {
super.init()
}
// MARK: - Factory Methods
@objc
public func initialViewController() -> UIViewController {
AssertIsOnMainThread()
let view = OnboardingSplashViewController(onboardingController: self)
return view
}
// MARK: - Transitions
public func onboardingSplashDidComplete(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
let view = OnboardingPermissionsViewController(onboardingController: self)
viewController.navigationController?.pushViewController(view, animated: true)
}
public func onboardingPermissionsWasSkipped(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
pushPhoneNumberView(viewController: viewController)
}
public func onboardingPermissionsDidComplete(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
pushPhoneNumberView(viewController: viewController)
}
private func pushPhoneNumberView(viewController: UIViewController) {
AssertIsOnMainThread()
let view = OnboardingPhoneNumberViewController(onboardingController: self)
viewController.navigationController?.pushViewController(view, animated: true)
}
public func onboardingPhoneNumberDidComplete(viewController: UIViewController) {
public func onboardingRegistrationSucceeded(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
// CodeVerificationViewController *vc = [CodeVerificationViewController new];
// [weakSelf.navigationController pushViewController:vc animated:YES];
}
public func onboardingDidRequireCaptcha(viewController: UIViewController) {
AssertIsOnMainThread()
Logger.info("")
guard let navigationController = viewController.navigationController else {
owsFailDebug("Missing navigationController.")
return
}
// The service could demand CAPTCHA from the "phone number" view or later
// from the "code verification" view. The "Captcha" view should always appear
// immediately after the "phone number" view.
while navigationController.viewControllers.count > 1 &&
!(navigationController.topViewController is OnboardingPhoneNumberViewController) {
navigationController.popViewController(animated: false)
}
let view = OnboardingCaptchaViewController(onboardingController: self)
navigationController.pushViewController(view, animated: true)
}
// MARK: - State
public private(set) var countryState: OnboardingCountryState = .defaultValue
public private(set) var phoneNumber: OnboardingPhoneNumber?
public private(set) var captchaToken: String?
@objc
public func update(countryState: OnboardingCountryState) {
AssertIsOnMainThread()
self.countryState = countryState
}
@objc
public func update(phoneNumber: OnboardingPhoneNumber) {
AssertIsOnMainThread()
self.phoneNumber = phoneNumber
}
@objc
public func update(captchaToken: String) {
AssertIsOnMainThread()
self.captchaToken = captchaToken
}
// MARK: - Debug
private static let kKeychainService_LastRegistered = "kKeychainService_LastRegistered"
private static let kKeychainKey_LastRegisteredCountryCode = "kKeychainKey_LastRegisteredCountryCode"
private static let kKeychainKey_LastRegisteredPhoneNumber = "kKeychainKey_LastRegisteredPhoneNumber"
private class func debugValue(forKey key: String) -> String? {
AssertIsOnMainThread()
guard OWSIsDebugBuild() else {
return nil
}
do {
let value = try CurrentAppContext().keychainStorage().string(forService: kKeychainService_LastRegistered, key: key)
return value
} catch {
owsFailDebug("Error: \(error)")
return nil
}
}
private class func setDebugValue(_ value: String, forKey key: String) {
AssertIsOnMainThread()
guard OWSIsDebugBuild() else {
return
}
do {
try CurrentAppContext().keychainStorage().set(string: value, service: kKeychainService_LastRegistered, key: key)
} catch {
owsFailDebug("Error: \(error)")
}
}
public class func lastRegisteredCountryCode() -> String? {
return debugValue(forKey: kKeychainKey_LastRegisteredCountryCode)
}
private class func setLastRegisteredCountryCode(value: String) {
setDebugValue(value, forKey: kKeychainKey_LastRegisteredCountryCode)
}
public class func lastRegisteredPhoneNumber() -> String? {
return debugValue(forKey: kKeychainKey_LastRegisteredPhoneNumber)
}
private class func setLastRegisteredPhoneNumber(value: String) {
setDebugValue(value, forKey: kKeychainKey_LastRegisteredPhoneNumber)
}
// MARK: - Registration
public func tryToRegister(fromViewController: UIViewController,
smsVerification: Bool) {
guard let phoneNumber = phoneNumber else {
owsFailDebug("Missing phoneNumber.")
return
}
// We eagerly update this state, regardless of whether or not the
// registration request succeeds.
OnboardingController.setLastRegisteredCountryCode(value: countryState.countryCode)
OnboardingController.setLastRegisteredPhoneNumber(value: phoneNumber.userInput)
let captchaToken = self.captchaToken
ModalActivityIndicatorViewController.present(fromViewController: fromViewController,
canCancel: true) { (modal) in
self.tsAccountManager.register(withPhoneNumber: phoneNumber.e164,
captchaToken: captchaToken,
success: {
DispatchQueue.main.async {
modal.dismiss(completion: {
self.registrationSucceeded(viewController: fromViewController)
})
}
}, failure: { (error) in
Logger.error("Error: \(error)")
DispatchQueue.main.async {
modal.dismiss(completion: {
self.registrationFailed(viewController: fromViewController, error: error as NSError)
})
}
}, smsVerification: smsVerification)
}
}
private func registrationSucceeded(viewController: UIViewController) {
onboardingRegistrationSucceeded(viewController: viewController)
}
private func registrationFailed(viewController: UIViewController, error: NSError) {
if error.code == 402 {
Logger.info("Captcha requested.")
onboardingDidRequireCaptcha(viewController: viewController)
return
} else if error.code == 400 {
OWSAlerts.showAlert(title: NSLocalizedString("REGISTRATION_ERROR", comment: ""),
message: NSLocalizedString("REGISTRATION_NON_VALID_NUMBER", comment: ""))
} else {
OWSAlerts.showAlert(title: error.localizedDescription,
message: error.localizedRecoverySuggestion)
}
}
}

@ -24,13 +24,6 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
override public func loadView() {
super.loadView()
// TODO: Is this still necessary?
if let navigationController = self.navigationController as? OWSNavigationController {
SignalApp.shared().signUpFlowNavigationController = navigationController
} else {
owsFailDebug("Missing or invalid navigationController")
}
populateDefaults()
view.backgroundColor = Theme.backgroundColor
@ -123,7 +116,6 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperviewMargins()
stackView.autoPinWidthToSuperviewMargins()
stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
@ -173,13 +165,13 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
owsFailDebug("Could not resume re-registration; couldn't parse phoneNumberE164.")
return
}
guard let callingCode = parsedPhoneNumber.getCountryCode() else {
guard let callingCodeNumeric = parsedPhoneNumber.getCountryCode() else {
owsFailDebug("Could not resume re-registration; missing callingCode.")
return
}
let callingCodeText = "\(COUNTRY_CODE_PREFIX)\(callingCode)"
let callingCode = "\(COUNTRY_CODE_PREFIX)\(callingCodeNumeric)"
let countryCodes: [String] =
PhoneNumberUtil.sharedThreadLocal().countryCodes(fromCallingCode: callingCodeText)
PhoneNumberUtil.sharedThreadLocal().countryCodes(fromCallingCode: callingCode)
guard let countryCode = countryCodes.first else {
owsFailDebug("Could not resume re-registration; unknown countryCode.")
return
@ -188,52 +180,11 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
owsFailDebug("Could not resume re-registration; unknown countryName.")
return
}
if !phoneNumberE164.hasPrefix(callingCodeText) {
if !phoneNumberE164.hasPrefix(callingCode) {
owsFailDebug("Could not resume re-registration; non-matching calling code.")
return
}
let phoneNumberWithoutCallingCode = phoneNumberE164.substring(from: callingCodeText.count)
update(withCountryName: countryName, callingCode: callingCodeText, countryCode: countryCode)
phoneNumberTextField.text = phoneNumberWithoutCallingCode
// Don't let user edit their phone number while re-registering.
phoneNumberTextField.isEnabled = false
}
// MARK: -
private var countryName = ""
private var callingCode = ""
private var countryCode = ""
private func populateDefaults() {
var countryCode: String = PhoneNumber.defaultCountryCode()
if let lastRegisteredCountryCode = self.lastRegisteredCountryCode(),
lastRegisteredCountryCode.count > 0 {
countryCode = lastRegisteredCountryCode
}
let callingCodeNumber: NSNumber = PhoneNumberUtil.sharedThreadLocal().nbPhoneNumberUtil.getCountryCode(forRegion: countryCode)
let callingCode = "\(COUNTRY_CODE_PREFIX)\(callingCodeNumber)"
if let lastRegisteredPhoneNumber = self.lastRegisteredPhoneNumber(),
lastRegisteredPhoneNumber.count > 0,
lastRegisteredPhoneNumber.hasPrefix(callingCode) {
phoneNumberTextField.text = lastRegisteredPhoneNumber.substring(from: callingCode.count)
}
var countryName = NSLocalizedString("UNKNOWN_COUNTRY_NAME", comment: "Label for unknown countries.")
if let countryNameDerived = PhoneNumberUtil.countryName(fromCountryCode: countryCode) {
countryName = countryNameDerived
}
update(withCountryName: countryName, callingCode: callingCode, countryCode: countryCode)
}
private func update(withCountryName countryName: String, callingCode: String, countryCode: String) {
AssertIsOnMainThread()
let phoneNumberWithoutCallingCode = phoneNumberE164.substring(from: callingCode.count)
guard countryCode.count > 0 else {
owsFailDebug("Invalid country code.")
@ -248,62 +199,55 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
return
}
self.countryName = countryName
self.callingCode = callingCode
self.countryCode = countryCode
let countryState = OnboardingCountryState(countryName: countryName, callingCode: callingCode, countryCode: countryCode)
onboardingController.update(countryState: countryState)
countryNameLabel.text = countryName
callingCodeLabel.text = callingCode
updateState()
self.phoneNumberTextField.placeholder = ViewControllerUtils.examplePhoneNumber(forCountryCode: countryCode, callingCode: callingCode)
phoneNumberTextField.text = phoneNumberWithoutCallingCode
// Don't let user edit their phone number while re-registering.
phoneNumberTextField.isEnabled = false
}
// MARK: - Debug
private let kKeychainService_LastRegistered = "kKeychainService_LastRegistered"
private let kKeychainKey_LastRegisteredCountryCode = "kKeychainKey_LastRegisteredCountryCode"
private let kKeychainKey_LastRegisteredPhoneNumber = "kKeychainKey_LastRegisteredPhoneNumber"
// MARK: -
private func debugValue(forKey key: String) -> String? {
guard OWSIsDebugBuild() else {
return nil
private var countryName: String {
get {
return onboardingController.countryState.countryName
}
}
private var callingCode: String {
get {
AssertIsOnMainThread()
do {
let value = try CurrentAppContext().keychainStorage().string(forService: kKeychainService_LastRegistered, key: key)
return value
} catch {
owsFailDebug("Error: \(error)")
return nil
return onboardingController.countryState.callingCode
}
}
private var countryCode: String {
get {
AssertIsOnMainThread()
private func setDebugValue(_ value: String, forKey key: String) {
guard OWSIsDebugBuild() else {
return
return onboardingController.countryState.countryCode
}
}
do {
try CurrentAppContext().keychainStorage().set(string: value, service: kKeychainService_LastRegistered, key: key)
} catch {
owsFailDebug("Error: \(error)")
private func populateDefaults() {
if let lastRegisteredPhoneNumber = OnboardingController.lastRegisteredPhoneNumber(),
lastRegisteredPhoneNumber.count > 0,
lastRegisteredPhoneNumber.hasPrefix(callingCode) {
phoneNumberTextField.text = lastRegisteredPhoneNumber.substring(from: callingCode.count)
}
}
private func lastRegisteredCountryCode() -> String? {
return debugValue(forKey: kKeychainKey_LastRegisteredCountryCode)
updateState()
}
private func setLastRegisteredCountryCode(value: String) {
setDebugValue(value, forKey: kKeychainKey_LastRegisteredCountryCode)
}
private func updateState() {
AssertIsOnMainThread()
private func lastRegisteredPhoneNumber() -> String? {
return debugValue(forKey: kKeychainKey_LastRegisteredPhoneNumber)
}
countryNameLabel.text = countryName
callingCodeLabel.text = callingCode
private func setLastRegisteredPhoneNumber(value: String) {
setDebugValue(value, forKey: kKeychainKey_LastRegisteredPhoneNumber)
self.phoneNumberTextField.placeholder = ViewControllerUtils.examplePhoneNumber(forCountryCode: countryCode, callingCode: callingCode)
}
// MARK: - Events
@ -368,10 +312,11 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
comment: "Message of alert indicating that users needs to enter a valid phone number to register."))
return
}
let parsedPhoneNumber = localNumber.toE164()
let e164PhoneNumber = localNumber.toE164()
onboardingController.update(phoneNumber: OnboardingPhoneNumber(e164: e164PhoneNumber, userInput: phoneNumberText))
if UIDevice.current.isIPad {
let countryCode = self.countryCode
OWSAlerts.showConfirmationAlert(title: NSLocalizedString("REGISTRATION_IPAD_CONFIRM_TITLE",
comment: "alert title when registering an iPad"),
message: NSLocalizedString("REGISTRATION_IPAD_CONFIRM_BODY",
@ -379,59 +324,11 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
proceedTitle: NSLocalizedString("REGISTRATION_IPAD_CONFIRM_BUTTON",
comment: "button text to proceed with registration when on an iPad"),
proceedAction: { (_) in
self.tryToRegister(parsedPhoneNumber: parsedPhoneNumber,
phoneNumberText: phoneNumberText,
countryCode: countryCode)
self.onboardingController.tryToRegister(fromViewController: self, smsVerification: false)
})
} else {
tryToRegister(parsedPhoneNumber: parsedPhoneNumber,
phoneNumberText: phoneNumberText,
countryCode: countryCode)
}
}
private func tryToRegister(parsedPhoneNumber: String,
phoneNumberText: String,
countryCode: String) {
ModalActivityIndicatorViewController.present(fromViewController: self,
canCancel: true) { (modal) in
self.setLastRegisteredCountryCode(value: countryCode)
self.setLastRegisteredPhoneNumber(value: phoneNumberText)
self.tsAccountManager.register(withPhoneNumber: parsedPhoneNumber,
success: {
DispatchQueue.main.async {
modal.dismiss(completion: {
self.registrationSucceeded()
})
}
}, failure: { (error) in
Logger.error("Error: \(error)")
DispatchQueue.main.async {
modal.dismiss(completion: {
self.registrationFailed(error: error as NSError)
})
}
}, smsVerification: true)
}
}
private func registrationSucceeded() {
self.onboardingController.onboardingPhoneNumberDidComplete(viewController: self)
}
private func registrationFailed(error: NSError) {
if error.code == 400 {
OWSAlerts.showAlert(title: NSLocalizedString("REGISTRATION_ERROR", comment: ""),
message: NSLocalizedString("REGISTRATION_NON_VALID_NUMBER", comment: ""))
} else {
OWSAlerts.showAlert(title: error.localizedDescription,
message: error.localizedRecoverySuggestion)
onboardingController.tryToRegister(fromViewController: self, smsVerification: false)
}
phoneNumberTextField.becomeFirstResponder()
}
}
@ -448,7 +345,6 @@ extension OnboardingPhoneNumberViewController: UITextFieldDelegate {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
parseAndTryToRegister()
textField.resignFirstResponder()
return false
}
}
@ -470,7 +366,11 @@ extension OnboardingPhoneNumberViewController: CountryCodeViewControllerDelegate
return
}
update(withCountryName: countryName, callingCode: callingCode, countryCode: countryCode)
let countryState = OnboardingCountryState(countryName: countryName, callingCode: callingCode, countryCode: countryCode)
onboardingController.update(countryState: countryState)
updateState()
// Trigger the formatting logic with a no-op edit.
_ = textField(phoneNumberTextField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "")

@ -448,6 +448,7 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi
__weak RegistrationViewController *weakSelf = self;
[self.tsAccountManager registerWithPhoneNumber:parsedPhoneNumber
captchaToken:nil
success:^{
OWSProdInfo([OWSAnalyticsEvents registrationRegisteredPhoneNumber]);

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "RegistrationUtils.h"
@ -59,8 +59,8 @@ NS_ASSUME_NONNULL_BEGIN
presentFromViewController:fromViewController
canCancel:NO
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
[self.tsAccountManager
registerWithPhoneNumber:self.tsAccountManager.reregisterationPhoneNumber
[self.tsAccountManager registerWithPhoneNumber:self.tsAccountManager.reregisterationPhoneNumber
captchaToken:nil
success:^{
OWSLogInfo(@"re-registering: send verification code succeeded.");

@ -1092,11 +1092,11 @@
/* Label for button that resets crop & rotation state. */
"IMAGE_EDITOR_RESET_BUTTON" = "Reset";
/* Label for button that rotates image 90 degrees. */
"IMAGE_EDITOR_ROTATE_90_BUTTON" = "Rotate 90°";
/* Label for button that rotates image 45 degrees. */
"IMAGE_EDITOR_ROTATE_45_BUTTON" = "Rotate 45°";
/* Label for button that rotates image 90 degrees. */
"IMAGE_EDITOR_ROTATE_45_BUTTON" = "Rotate 45°";
"IMAGE_EDITOR_ROTATE_90_BUTTON" = "Rotate 90°";
/* Momentarily shown to the user when attempting to select more images than is allowed. Embeds {{max number of items}} that can be shared. */
"IMAGE_PICKER_CAN_SELECT_NO_MORE_TOAST_FORMAT" = "You can't share more than %@ items.";
@ -1508,6 +1508,9 @@
/* No comment provided by engineer. */
"OK" = "OK";
/* Title of the 'onboarding Captcha' view. */
"ONBOARDING_CAPTCHA_TITLE" = "We need to verify that you're human";
/* Explanation in the 'onboarding permissions' view. */
"ONBOARDING_PERMISSIONS_EXPLANATION" = "ONBOARDING_PERMISSIONS_EXPLANATION";

@ -90,13 +90,18 @@ typedef NS_ENUM(NSUInteger, OWSRegistrationState) {
#pragma mark - Register with phone number
- (void)registerWithPhoneNumber:(NSString *)phoneNumber
captchaToken:(nullable NSString *)captchaToken
success:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock
smsVerification:(BOOL)isSMS;
- (void)rerequestSMSWithSuccess:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock;
- (void)rerequestSMSWithCaptchaToken:(nullable NSString *)captchaToken
success:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock;
- (void)rerequestVoiceWithSuccess:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock;
- (void)rerequestVoiceWithCaptchaToken:(nullable NSString *)captchaToken
success:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock;
- (void)verifyAccountWithCode:(NSString *)verificationCode
pin:(nullable NSString *)pin

@ -322,6 +322,7 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
}
- (void)registerWithPhoneNumber:(NSString *)phoneNumber
captchaToken:(nullable NSString *)captchaToken
success:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock
smsVerification:(BOOL)isSMS
@ -339,6 +340,7 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
TSRequest *request =
[OWSRequestFactory requestVerificationCodeRequestWithPhoneNumber:phoneNumber
captchaToken:captchaToken
transport:(isSMS ? TSVerificationTransportSMS
: TSVerificationTransportVoice)];
[[TSNetworkManager sharedManager] makeRequest:request
@ -357,20 +359,33 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
}];
}
- (void)rerequestSMSWithSuccess:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock
- (void)rerequestSMSWithCaptchaToken:(nullable NSString *)captchaToken
success:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock
{
// TODO: Can we remove phoneNumberAwaitingVerification?
NSString *number = self.phoneNumberAwaitingVerification;
OWSAssertDebug(number);
[self registerWithPhoneNumber:number success:successBlock failure:failureBlock smsVerification:YES];
[self registerWithPhoneNumber:number
captchaToken:captchaToken
success:successBlock
failure:failureBlock
smsVerification:YES];
}
- (void)rerequestVoiceWithSuccess:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock
- (void)rerequestVoiceWithCaptchaToken:(nullable NSString *)captchaToken
success:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock
{
NSString *number = self.phoneNumberAwaitingVerification;
OWSAssertDebug(number);
[self registerWithPhoneNumber:number success:successBlock failure:failureBlock smsVerification:NO];
[self registerWithPhoneNumber:number
captchaToken:captchaToken
success:successBlock
failure:failureBlock
smsVerification:NO];
}
- (void)verifyAccountWithCode:(NSString *)verificationCode

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@ -56,6 +56,7 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo
+ (TSRequest *)unregisterAccountRequest;
+ (TSRequest *)requestVerificationCodeRequestWithPhoneNumber:(NSString *)phoneNumber
captchaToken:(nullable NSString *)captchaToken
transport:(TSVerificationTransport)transport;
+ (TSRequest *)submitMessageRequestWithRecipient:(NSString *)recipientId

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSRequestFactory.h"
@ -235,13 +235,27 @@ NS_ASSUME_NONNULL_BEGIN
}
+ (TSRequest *)requestVerificationCodeRequestWithPhoneNumber:(NSString *)phoneNumber
captchaToken:(nullable NSString *)captchaToken
transport:(TSVerificationTransport)transport
{
OWSAssertDebug(phoneNumber.length > 0);
NSString *path = [NSString stringWithFormat:@"%@/%@/code/%@?client=ios",
NSString *querystring = @"client=ios";
if (captchaToken.length > 0) {
querystring = [NSString stringWithFormat:@"%@&captcha=%@", querystring, captchaToken];
}
NSURLQueryItem *screenNameItem, *includeRTsItem;
screenNameItem = [NSURLQueryItem queryItemWithName:@"screen_name" value:@"joemasilotti"];
includeRTsItem = [NSURLQueryItem queryItemWithName:@"include_rts" value:@"true"];
components.queryItems = @[ screenNameItem, includeRTsItem ];
url = components.URL;
NSString *path = [NSString stringWithFormat:@"%@/%@/code/%@?%@",
textSecureAccountsAPI,
[self stringForTransport:transport],
phoneNumber];
phoneNumber,
querystring];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
request.shouldHaveAuthorizationHeaders = NO;

Loading…
Cancel
Save