From 175058bf3444d17c0cfad5f6e3c861af207ebd3d Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 24 Oct 2021 13:53:08 +0300 Subject: [PATCH 1/2] Handle Realm init error with a modal --- FlowCrypt.xcodeproj/project.pbxproj | 4 + .../InvalidStorageViewController.swift | 74 +++++++++++++++++++ .../Encrypted Storage/EncryptedStorage.swift | 13 +++- .../Functionality/Services/GlobalRouter.swift | 21 +++++- .../Resources/en.lproj/Localizable.strings | 6 ++ 5 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 9076fb10e..5fa5b19f8 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 21FEE26626FDD91A00E3783F /* ComposeMessageAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21FEE26526FDD91A00E3783F /* ComposeMessageAttachment.swift */; }; 2C2A3B4B2719EE6100B7F27B /* KeyServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2A3B4A2719EE6100B7F27B /* KeyServiceTests.swift */; }; 2C2A3B4D2719EF7300B7F27B /* PassPhraseServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2A3B4C2719EF7300B7F27B /* PassPhraseServiceMock.swift */; }; + 2C60AB0C272564D40040D7F2 /* InvalidStorageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C60AB0B272564D40040D7F2 /* InvalidStorageViewController.swift */; }; 32DCA00224982EDA88D69C6E /* AppErr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA4B11D4531B3B04D01D1 /* AppErr.swift */; }; 32DCA04CA0DAB79C39514782 /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; 32DCA1414EEA727B86C337D5 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; @@ -442,6 +443,7 @@ 21FEE26526FDD91A00E3783F /* ComposeMessageAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMessageAttachment.swift; sourceTree = ""; }; 2C2A3B4A2719EE6100B7F27B /* KeyServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyServiceTests.swift; sourceTree = ""; }; 2C2A3B4C2719EF7300B7F27B /* PassPhraseServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhraseServiceMock.swift; sourceTree = ""; }; + 2C60AB0B272564D40040D7F2 /* InvalidStorageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidStorageViewController.swift; sourceTree = ""; }; 32DCA058652FD4616FB04FB6 /* SequenceExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SequenceExtensions.swift; sourceTree = ""; }; 32DCA0C3D34A69851A238E87 /* Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = ""; }; 32DCA0E63F2F0473D0A8EDB0 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; @@ -1912,6 +1914,7 @@ isa = PBXGroup; children = ( D24F4C2123E2359B00C5EEE4 /* BootstrapViewController.swift */, + 2C60AB0B272564D40040D7F2 /* InvalidStorageViewController.swift */, ); path = Bootstrap; sourceTree = ""; @@ -2578,6 +2581,7 @@ 32DCA1B95DDC04D671F662F8 /* URLSessionExtension.swift in Sources */, 214A023A26A3029700C24066 /* EmailKeyManagerApi.swift in Sources */, D2E26F7424F2705B00612AF1 /* ContactDetailDecorator.swift in Sources */, + 2C60AB0C272564D40040D7F2 /* InvalidStorageViewController.swift in Sources */, 21C7DF0526697DA500C44800 /* PromiseKitExtension.swift in Sources */, 9F7E8F19269C538E0021C07F /* NavigationChildController.swift in Sources */, 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */, diff --git a/FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift b/FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift new file mode 100644 index 000000000..a17849a06 --- /dev/null +++ b/FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift @@ -0,0 +1,74 @@ +// +// InvalidStorageViewController.swift +// FlowCrypt +// +// Created by  Ivan Ushakov on 24.10.2021 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import AsyncDisplayKit +import FlowCryptUI + +final class InvalidStorageViewController: TableNodeViewController { + private let encryptedStorage: EncryptedStorageType + private let router: GlobalRouterType + + init(encryptedStorage: EncryptedStorageType, router: GlobalRouterType) { + self.encryptedStorage = encryptedStorage + self.router = router + super.init(node: TableNode()) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + title = "invalid_storage_title".localized + + node.delegate = self + node.dataSource = self + node.reloadData() + } + + private func handleTap() { + do { + try encryptedStorage.reset() + router.proceed() + } catch { + showAlert(message: "invalid_storage_reset_error".localized) + } + } +} + +extension InvalidStorageViewController: ASTableDelegate, ASTableDataSource { + func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { + 2 + } + + func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { + { + if indexPath.row == 0 { + return KeyTextCellNode( + title: "invalid_storage_text".localized.attributed(.regular(12), color: .black), + insets: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) + ) + } + + if indexPath.row == 1 { + let input = ButtonCellNode.Input( + title: "invalid_storage_reset_button".localized.attributed(.bold(16), color: .white), + insets: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16), + color: .red + ) + return ButtonCellNode(input: input) { [weak self] in + self?.handleTap() + } + } + + return ASCellNode() + } + } +} diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index 3be3512c4..9d10b551d 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -20,6 +20,8 @@ protocol EncryptedStorageType: KeyStorageType { var activeUser: UserObject? { get } func doesAnyKeyExist(for email: String) -> Bool + func validate() throws + func reset() throws func cleanup() } @@ -79,7 +81,6 @@ final class EncryptedStorage: EncryptedStorageType { let realm = try Realm(configuration: encryptedConfiguration) return realm } catch { -// destroyEncryptedStorage() - todo - give user option to wipe, don't do it automatically fatalError("failed to initiate realm: \(error)") } } @@ -254,6 +255,16 @@ extension EncryptedStorage { } extension EncryptedStorage { + func validate() throws { + Realm.Configuration.defaultConfiguration = encryptedConfiguration + _ = try Realm(configuration: encryptedConfiguration) + } + + func reset() throws { + let path = getDocumentDirectory() + "/" + Constants.encryptedDbFilename + try FileManager.default.removeItem(atPath: path) + } + func cleanup() { do { try storage.write { diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index e9286e976..9bd17f0a0 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -56,8 +56,25 @@ final class GlobalRouter: GlobalRouterType { extension GlobalRouter { /// proceed to flow (signing/setup/app) depends on user status (isLoggedIn/isSetupFinished) func proceed() { - userAccountService.cleanupSessions() - proceed(with: nil) + validateEncryptedStorage { + userAccountService.cleanupSessions() + proceed(with: nil) + } + } + + private func validateEncryptedStorage(_ completion: () -> Void) { + let storage = EncryptedStorage() + do { + try storage.validate() + completion() + } catch { + let controller = InvalidStorageViewController( + encryptedStorage: storage, + router: self + ) + keyWindow.rootViewController = UINavigationController(rootViewController: controller) + keyWindow.makeKeyAndVisible() + } } private func proceed(with session: SessionType?) { diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index d55756fba..861797165 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -251,3 +251,9 @@ "files_picking_photos_error_message" = "Could not add a photo"; "files_picking_size_error_message" = "Total attachments size can not exceed 10 MB"; +// Invalid storage view controller +"invalid_storage_title" = "Error"; +"invalid_storage_text" = "Your data storage is invalid. You could reset it and start with new one"; +"invalid_storage_reset_button" = "Reset"; +"invalid_storage_reset_error" = "Couldn't remove storage. Please reinstall application"; + From 5e45e5c4fd7d77f5fc011111f68780381426540f Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 24 Oct 2021 22:05:01 +0300 Subject: [PATCH 2/2] Handle Realm init error with a modal --- .../InvalidStorageViewController.swift | 86 ++++++++++--------- .../Functionality/Services/GlobalRouter.swift | 1 + .../Resources/en.lproj/Localizable.strings | 2 +- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift b/FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift index a17849a06..0af96495b 100644 --- a/FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift +++ b/FlowCrypt/Controllers/Bootstrap/InvalidStorageViewController.swift @@ -6,17 +6,18 @@ // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // -import AsyncDisplayKit -import FlowCryptUI +import UIKit -final class InvalidStorageViewController: TableNodeViewController { +final class InvalidStorageViewController: UIViewController { + private let error: Error private let encryptedStorage: EncryptedStorageType private let router: GlobalRouterType - init(encryptedStorage: EncryptedStorageType, router: GlobalRouterType) { + init(error: Error, encryptedStorage: EncryptedStorageType, router: GlobalRouterType) { + self.error = error self.encryptedStorage = encryptedStorage self.router = router - super.init(node: TableNode()) + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -28,12 +29,49 @@ final class InvalidStorageViewController: TableNodeViewController { super.viewDidLoad() title = "invalid_storage_title".localized - node.delegate = self - node.dataSource = self - node.reloadData() + view.backgroundColor = .white + + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = "invalid_storage_text".localized + label.numberOfLines = 0 + label.font = UIFont.systemFont(ofSize: 16) + view.addSubview(label) + + let textView = UITextView() + textView.translatesAutoresizingMaskIntoConstraints = false + textView.isEditable = false + textView.text = error.localizedDescription + view.addSubview(textView) + + let button = UIButton(type: .custom) + button.translatesAutoresizingMaskIntoConstraints = false + button.backgroundColor = .red + button.setTitle("invalid_storage_reset_button".localized, for: .normal) + button.setTitleColor(.white, for: .normal) + button.addTarget(self, action: #selector(handleTap), for: .touchUpInside) + button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold) + button.layer.cornerRadius = 5 + view.addSubview(button) + + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8), + label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16), + label.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16), + + textView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 10), + textView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16), + textView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16), + textView.bottomAnchor.constraint(equalTo: button.topAnchor, constant: -10), + + button.heightAnchor.constraint(equalToConstant: 50), + button.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16), + button.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16), + button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -8) + ]) } - private func handleTap() { + @objc private func handleTap() { do { try encryptedStorage.reset() router.proceed() @@ -42,33 +80,3 @@ final class InvalidStorageViewController: TableNodeViewController { } } } - -extension InvalidStorageViewController: ASTableDelegate, ASTableDataSource { - func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { - 2 - } - - func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { - { - if indexPath.row == 0 { - return KeyTextCellNode( - title: "invalid_storage_text".localized.attributed(.regular(12), color: .black), - insets: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) - ) - } - - if indexPath.row == 1 { - let input = ButtonCellNode.Input( - title: "invalid_storage_reset_button".localized.attributed(.bold(16), color: .white), - insets: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16), - color: .red - ) - return ButtonCellNode(input: input) { [weak self] in - self?.handleTap() - } - } - - return ASCellNode() - } - } -} diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index 9bd17f0a0..1268ac263 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -69,6 +69,7 @@ extension GlobalRouter { completion() } catch { let controller = InvalidStorageViewController( + error: error, encryptedStorage: storage, router: self ) diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 861797165..917e01f67 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -253,7 +253,7 @@ // Invalid storage view controller "invalid_storage_title" = "Error"; -"invalid_storage_text" = "Your data storage is invalid. You could reset it and start with new one"; +"invalid_storage_text" = "Your data storage is invalid. You could reset it and start with new one. Check error description below"; "invalid_storage_reset_button" = "Reset"; "invalid_storage_reset_error" = "Couldn't remove storage. Please reinstall application";