├── .bundle └── config ├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── auto-merge.yml │ ├── check-for-emails.yml │ └── codeql.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── .semaphore └── semaphore.yml ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── .vscode └── settings.json ├── Core ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .vscode │ ├── settings.json │ └── tasks.json ├── README.md ├── eslint-local-rules.js ├── package-lock.json ├── package.json ├── source │ ├── assets │ │ ├── compat │ │ │ ├── direct-encrypted-key-mismatch-file.txt │ │ │ ├── direct-encrypted-pgpmime-special-chars.txt │ │ │ ├── direct-encrypted-pgpmime.txt │ │ │ ├── direct-encrypted-text-special-chars.txt │ │ │ ├── direct-encrypted-text.txt │ │ │ ├── mime-email-encrypted-inline-image.txt │ │ │ ├── mime-email-encrypted-inline-pgpmime.txt │ │ │ ├── mime-email-encrypted-inline-text-2.txt │ │ │ ├── mime-email-encrypted-inline-text-signed.txt │ │ │ ├── mime-email-encrypted-inline-text.txt │ │ │ ├── mime-email-not-integrity-protected.txt │ │ │ ├── mime-email-plain-html.txt │ │ │ ├── mime-email-plain-iso-2201-jp.txt │ │ │ ├── mime-email-plain-signed-detached.txt │ │ │ ├── mime-email-plain-signed-edited.txt │ │ │ ├── mime-email-plain-signed.txt │ │ │ ├── mime-email-plain-with-attachment.txt │ │ │ ├── mime-email-plain-with-pubkey.txt │ │ │ └── mime-email-plain.txt │ │ └── html │ │ │ └── leak.html │ ├── core │ │ ├── att.ts │ │ ├── buf.ts │ │ ├── common.ts │ │ ├── const.ts │ │ ├── mime.ts │ │ ├── mnemonic.ts │ │ ├── msg-block-parser.ts │ │ ├── msg-block.ts │ │ ├── pgp-armor.ts │ │ ├── pgp-key.ts │ │ ├── pgp-msg.ts │ │ ├── pgp-password.ts │ │ ├── pgp.ts │ │ └── types │ │ │ └── emailjs.d.ts │ ├── entrypoint-bare.ts │ ├── gen-compat-assets.ts │ ├── lib │ │ ├── emailjs │ │ │ ├── emailjs-addressparser.js │ │ │ ├── emailjs-mime-builder.js │ │ │ ├── emailjs-mime-codec.js │ │ │ ├── emailjs-mime-parser.js │ │ │ ├── emailjs-mime-types.js │ │ │ ├── emailjs-stringencoding.js │ │ │ └── punycode.js │ │ └── iso-8859-2.js │ ├── mobile-interface │ │ ├── endpoints.ts │ │ ├── format-output.ts │ │ └── validate-input.ts │ ├── platform │ │ ├── catch.ts │ │ ├── require.ts │ │ ├── store.ts │ │ ├── util.ts │ │ └── xss.ts │ ├── test.ts │ └── test │ │ └── test-utils.ts ├── tooling │ ├── build-final.js │ ├── build.sh │ └── fix-bundles.js ├── tsconfig.eslint.json ├── tsconfig.json └── webpack.bare.config.js ├── FlowCrypt.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ ├── IDETemplateMacros.plist │ └── xcschemes │ ├── Debug FlowCrypt.xcscheme │ ├── Enterprise FlowCrypt.xcscheme │ ├── FlowCrypt.xcscheme │ ├── FlowCryptAppTests.xcscheme │ ├── FlowCryptCommon.xcscheme │ ├── FlowCryptUI.xcscheme │ └── FlowCryptUIApplication.xcscheme ├── FlowCrypt ├── App │ ├── AppContext.swift │ ├── AppDelegate.swift │ ├── AppErr.swift │ ├── AppStartup.swift │ ├── GeneralConstants.swift │ └── GlobalRouter.swift ├── Assets.xcassets │ ├── AppIcon-Debug.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ ├── AppIcon-Enterprise.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ ├── AppIcon-TestFlight.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ ├── Contents.json │ ├── arrow_down.imageset │ │ ├── Contents.json │ │ └── arrow_down.png │ ├── arrow_up.imageset │ │ ├── Contents.json │ │ └── arrow_up.png │ ├── cancel.imageset │ │ ├── Contents.json │ │ └── cancel.png │ ├── copy.imageset │ │ ├── Contents.json │ │ └── copy.png │ ├── download.imageset │ │ ├── Contents.json │ │ ├── download-2-5.png │ │ ├── download-2-6.png │ │ └── download-2-7.png │ ├── email_icn.imageset │ │ ├── Contents.json │ │ └── icons8-important-mail-50.png │ ├── floating_icn.imageset │ │ ├── Contents.json │ │ ├── floating_icn.png │ │ ├── floating_icn@2x.png │ │ └── floating_icn@3x.png │ ├── flowcrypt-ios-icon.imageset │ │ ├── Contents.json │ │ └── flowcrypt-ios-icon.png │ ├── full-logo.imageset │ │ ├── 1200-312-black-and-white-dark-mode.png │ │ ├── Contents.json │ │ └── full-logo.png │ ├── gmail_icn.imageset │ │ ├── Contents.json │ │ ├── gmail.png │ │ ├── gmail@2x.png │ │ └── gmail@3x.png │ ├── microsoft-outlook.imageset │ │ ├── Contents.json │ │ └── microsoft-outlook@3x.png │ ├── plus.imageset │ │ ├── Contents.json │ │ └── plus.png │ ├── reply-all.imageset │ │ ├── Contents.json │ │ ├── reply-all.png │ │ ├── reply-all@2x.png │ │ └── reply-all@3x.png │ ├── retry.imageset │ │ ├── Contents.json │ │ ├── retry.png │ │ └── retry_white.png │ └── share.imageset │ │ ├── Contents.json │ │ └── share-2.png ├── Colors.xcassets │ └── Contents.json ├── Common UI │ ├── AttachmentManager.swift │ ├── CommonNodesInputs.swift │ ├── Refreshable.swift │ └── View Controllers │ │ ├── BlurViewController.swift │ │ └── WebViewController.swift ├── Controllers │ ├── Attachment │ │ └── AttachmentViewController.swift │ ├── Bootstrap │ │ ├── BootstrapViewController.swift │ │ └── InvalidStorageViewController.swift │ ├── CheckMailAuth │ │ ├── CheckMailAuthViewController.swift │ │ └── CheckMailAuthViewDecorator.swift │ ├── Compose │ │ ├── ComposeMessageAction.swift │ │ ├── ComposeRecipientPopupViewController.swift │ │ ├── ComposeViewController.swift │ │ ├── ComposeViewControllerInput.swift │ │ ├── ComposeViewDecorator.swift │ │ └── Extensions │ │ │ ├── ComposeViewController+ActionHandling.swift │ │ │ ├── ComposeViewController+Attachment.swift │ │ │ ├── ComposeViewController+Contacts.swift │ │ │ ├── ComposeViewController+Drafts.swift │ │ │ ├── ComposeViewController+ErrorHandling.swift │ │ │ ├── ComposeViewController+Keyboard.swift │ │ │ ├── ComposeViewController+MessageSend.swift │ │ │ ├── ComposeViewController+Nodes.swift │ │ │ ├── ComposeViewController+Picker.swift │ │ │ ├── ComposeViewController+RecipientInput.swift │ │ │ ├── ComposeViewController+RecipientPopup.swift │ │ │ ├── ComposeViewController+Setup.swift │ │ │ ├── ComposeViewController+State.swift │ │ │ ├── ComposeViewController+TableView.swift │ │ │ └── ComposeViewController+TapActions.swift │ ├── Inbox │ │ ├── Container │ │ │ ├── InboxViewContainerController.swift │ │ │ └── InboxViewControllerContainerDecorator.swift │ │ ├── InboxItem.swift │ │ ├── InboxProviders.swift │ │ ├── InboxViewController+Factory.swift │ │ ├── InboxViewController+State.swift │ │ ├── InboxViewController+TableView.swift │ │ ├── InboxViewController.swift │ │ └── InboxViewDecorator.swift │ ├── MessageList Extension │ │ └── MsgListViewConroller.swift │ ├── Search │ │ └── SearchViewController.swift │ ├── Settings │ │ ├── Backup │ │ │ ├── Backups Option Scene │ │ │ │ ├── BackupOptionsViewController.swift │ │ │ │ └── BackupOptionsViewDecorator.swift │ │ │ ├── Backups Scene │ │ │ │ ├── BackupViewController.swift │ │ │ │ └── BackupViewDecorator.swift │ │ │ └── Backups Seleckt Key Scene │ │ │ │ ├── BackupSelectKeyDecorator.swift │ │ │ │ └── BackupSelectKeyViewController.swift │ │ ├── Contacts │ │ │ ├── Contact Add │ │ │ │ ├── ContactAddViewController.swift │ │ │ │ └── ContactPublicKeyListViewController.swift │ │ │ └── Contacts List │ │ │ │ ├── ContactDetailDecorator.swift │ │ │ │ ├── ContactDetailViewController.swift │ │ │ │ ├── ContactKeyDetailDecorator.swift │ │ │ │ ├── ContactKeyDetailViewController.swift │ │ │ │ ├── ContactsListDecorator.swift │ │ │ │ └── ContactsListViewController.swift │ │ ├── KeyLegal │ │ │ ├── LegalViewController.swift │ │ │ └── LegalViewDecorator.swift │ │ ├── KeySettings │ │ │ ├── Key Detail Info │ │ │ │ ├── KeyDetailInfoViewController.swift │ │ │ │ └── KeyDetailInfoViewDecorator.swift │ │ │ ├── Key Details │ │ │ │ ├── KeyDetailViewController.swift │ │ │ │ └── KeyDetailViewDecorator.swift │ │ │ ├── Key List │ │ │ │ ├── KeySettingsViewController.swift │ │ │ │ └── KeySettingsViewDecorator.swift │ │ │ └── Public Key │ │ │ │ └── PublicKeyDetailViewController.swift │ │ └── Settings List │ │ │ ├── SettingsViewController.swift │ │ │ └── SettingsViewDecorator.swift │ ├── Setup │ │ ├── CreateKeyError.swift │ │ ├── PassPhraseSaveable.swift │ │ ├── SetupBackupsViewController.swift │ │ ├── SetupCreatePassphraseAbstractViewController.swift │ │ ├── SetupEKMKeyViewController.swift │ │ ├── SetupGenerateKeyViewController.swift │ │ ├── SetupInitialViewController.swift │ │ ├── SetupManuallyEnterPassPhraseViewController.swift │ │ ├── SetupManuallyImportKeyViewController.swift │ │ └── SetupViewDecorator.swift │ ├── SetupImap │ │ ├── SetupImapViewController.swift │ │ └── SetupImapViewDecorator.swift │ ├── SideMenu │ │ ├── Main │ │ │ └── SideMenuNavigationController.swift │ │ ├── Menu │ │ │ ├── MyMenuViewController.swift │ │ │ └── MyMenuViewDecorator.swift │ │ └── NavigationController │ │ │ ├── MainNavigationController.swift │ │ │ └── NavigationChildController.swift │ ├── SignIn │ │ ├── SignInViewController.swift │ │ └── SignInViewDecorator.swift │ └── Threads │ │ ├── Extensions │ │ ├── MessageActionsHandler.swift │ │ └── ThreadDetailsDecorator.swift │ │ ├── Helpers │ │ └── MessageActionsHelper.swift │ │ ├── Models │ │ ├── AlertsFactory.swift │ │ ├── MessageAction.swift │ │ └── MessageThread.swift │ │ ├── ThreadDetailsViewController+MessageActionsHandler.swift │ │ ├── ThreadDetailsViewController+TableView.swift │ │ ├── ThreadDetailsViewController+TapActions.swift │ │ └── ThreadDetailsViewController.swift ├── Core │ ├── Core.swift │ ├── CoreTypes.swift │ └── Models │ │ ├── DecryptedPrivateKey.swift │ │ ├── KeyAlgo.swift │ │ ├── KeyDetails.swift │ │ └── KeyId.swift ├── Extensions │ └── UIColorExtensions.swift ├── FlowCrypt-Bridging-Header.h ├── FlowCrypt.entitlements ├── FlowCryptEnterprise.entitlements ├── FlowCryptRelease.entitlements ├── Functionality │ ├── Api │ │ ├── Account Server Apis │ │ │ ├── BackendApi.swift │ │ │ ├── EnterpriseServerApi.swift │ │ │ ├── EnterpriseServerApiHelper.swift │ │ │ └── Models │ │ │ │ ├── EnterpriseServerApiError.swift │ │ │ │ ├── MessageUploadDetails.swift │ │ │ │ └── MultipartDataRequest.swift │ │ ├── ApiCall.swift │ │ ├── IdTokenUtils.swift │ │ ├── Remote Private Key Apis │ │ │ └── EmailKeyManagerApi.swift │ │ └── Remote Pub Key Apis │ │ │ ├── AttesterApi.swift │ │ │ ├── PubLookup.swift │ │ │ ├── WkdApi.swift │ │ │ └── WkdUrlConstructor.swift │ ├── DataManager │ │ ├── Encrypted Storage │ │ │ ├── EncryptedStorage.swift │ │ │ ├── SchemaMigration.swift │ │ │ ├── StorageEncryptionKeyProvider.swift │ │ │ ├── Version5SchemaMigration.swift │ │ │ └── Version6SchemaMigration.swift │ │ ├── Local Storage │ │ │ └── LocalStorage.swift │ │ └── SessionManager.swift │ ├── FilesManager │ │ └── FilesManager.swift │ ├── Mail Provider │ │ ├── Backup Provider │ │ │ ├── BackupApiClient.swift │ │ │ ├── Gmail+Backup.swift │ │ │ └── Imap+Backup.swift │ │ ├── Contacts Provider │ │ │ ├── ContactsProviderType.swift │ │ │ └── GoogleContactsProvider.swift │ │ ├── Gmail │ │ │ ├── GmailApiError.swift │ │ │ └── GmailService.swift │ │ ├── Imap │ │ │ ├── Imap+Other.swift │ │ │ ├── Imap+messages.swift │ │ │ ├── Imap+msg.swift │ │ │ ├── Imap+retry.swift │ │ │ ├── Imap+session.swift │ │ │ ├── Imap.swift │ │ │ ├── ImapError.swift │ │ │ ├── ImapHelper.swift │ │ │ ├── ImapSessionProvider.swift │ │ │ └── MessageKindProviderType.swift │ │ ├── Mail Sessions Providers │ │ │ ├── ConnectionType.swift │ │ │ ├── IMAPConnectionParameters.swift │ │ │ ├── MailSettingsCredentials.swift │ │ │ ├── SMTPSession.swift │ │ │ ├── SessionCredentialsProvider.swift │ │ │ └── providers_custom.json │ │ ├── MailProvider.swift │ │ ├── MailServiceProviderType.swift │ │ ├── Message Gateway │ │ │ ├── GmailService+draft.swift │ │ │ ├── GmailService+send.swift │ │ │ ├── Imap+send.swift │ │ │ └── MessageGateway.swift │ │ ├── Message Provider │ │ │ ├── Gmail+Message.swift │ │ │ ├── Gmail+MessageExtension.swift │ │ │ ├── Imap+Message.swift │ │ │ ├── MessageAttachment.swift │ │ │ ├── MessageHelper.swift │ │ │ ├── MessageProvider.swift │ │ │ └── ProcessedMessage.swift │ │ ├── MessageOperations Provider │ │ │ ├── Gmail+MessageOperations.swift │ │ │ ├── Imap+MessageOperations.swift │ │ │ └── MessageOperationsApiClient.swift │ │ ├── MessagesList Provider │ │ │ ├── Gmail+MessagesList.swift │ │ │ ├── Imap+MessagesList.swift │ │ │ ├── MessagesListApiClient.swift │ │ │ └── Model │ │ │ │ ├── Message.swift │ │ │ │ ├── MessageContext.swift │ │ │ │ ├── MessageIdentifier.swift │ │ │ │ ├── MessageLabel.swift │ │ │ │ └── MessageQuoteType.swift │ │ ├── SearchMessage Provider │ │ │ ├── Gmail+Search.swift │ │ │ ├── Imap+Search.swift │ │ │ └── MessageSearchApiClient.swift │ │ ├── Threads │ │ │ ├── Imap+ThreadOperations.swift │ │ │ ├── MessagesThreadApiClient.swift │ │ │ └── MessagesThreadOperationsApiClient.swift │ │ └── UsersMailSession Provider │ │ │ └── UserMailSessionProvider.swift │ ├── Pgp │ │ └── KeyMethods.swift │ ├── PhotosManager │ │ └── PhotosManager.swift │ └── Services │ │ ├── Backups Manager │ │ ├── BackupsManager.swift │ │ ├── BackupsManagerError.swift │ │ └── BackupsManagerType.swift │ │ ├── Client Configuration Provider │ │ ├── ClientConfiguration.swift │ │ ├── ClientConfigurationProvider.swift │ │ └── LocalClientConfiguration.swift │ │ ├── Compose Message Helper │ │ ├── ComposeMessageContext.swift │ │ ├── ComposeMessageError.swift │ │ ├── ComposeMessageHelper+State.swift │ │ ├── ComposeMessageHelper.swift │ │ └── ComposeMessageRecipient.swift │ │ ├── EKMVcHelper.swift │ │ ├── Folders Manager │ │ ├── FoldersManager.swift │ │ ├── LocalFoldersProvider.swift │ │ ├── Models │ │ │ └── FolderViewModel.swift │ │ ├── RemoteFoldersApiClient │ │ │ ├── GmailService+folders.swift │ │ │ ├── Imap+folders.swift │ │ │ └── RemoteFoldersApiClient.swift │ │ └── TrashFolderProvider.swift │ │ ├── Google User Service │ │ ├── GoogleAuthManager.swift │ │ ├── GoogleScope.swift │ │ └── IdToken.swift │ │ ├── Local Contacts Provider │ │ └── LocalContactsProvider.swift │ │ ├── Local Private Key Storage │ │ ├── CombinedPassPhraseStorage.swift │ │ ├── InMemoryPassPhraseStorage.swift │ │ └── KeyAndPassPhraseStorage.swift │ │ └── SendAs Provider │ │ ├── LocalSendAsProvider.swift │ │ ├── Models │ │ └── SendAsModel.swift │ │ ├── RemoteSendAsApiClient │ │ ├── GmailService+SendAs.swift │ │ └── RemoteSendAsApiClient.swift │ │ └── SendAsProvider.swift ├── Info.plist ├── Lib │ └── ObjcException │ │ ├── ObjcException.h │ │ └── ObjcException.m ├── Models │ ├── Common │ │ ├── Folder.swift │ │ ├── Keypair.swift │ │ ├── RawClientConfiguration.swift │ │ ├── Recipient.swift │ │ ├── RecipientBase.swift │ │ ├── Session.swift │ │ └── User.swift │ ├── Contact Models │ │ ├── PubKey.swift │ │ ├── PubKeyState.swift │ │ └── RecipientWithSortedPubKeys.swift │ ├── Document.swift │ ├── Inbox Models │ │ └── InboxViewModel.swift │ └── Realm Models │ │ ├── ClientConfigurationRealmObject.swift │ │ ├── FolderRealmObject.swift │ │ ├── KeypairRealmObject.swift │ │ ├── PubKeyRealmObject.swift │ │ ├── RecipientRealmObject.swift │ │ ├── SendAsRealmObject.swift │ │ ├── SessionRealmObject.swift │ │ └── UserRealmObject.swift └── Resources │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Localizable.stringsdict │ ├── Naming Convention.md │ ├── UI Guidance.md │ ├── en.lproj │ └── Localizable.strings │ ├── flowcrypt-ios-icon.png │ ├── generated │ └── flowcrypt-ios-prod.js.txt │ ├── pass_phrase_hint.html │ ├── ru.lproj │ ├── LaunchScreen.strings │ └── Localizable.strings │ └── simple_webview_file.html ├── FlowCryptAppTests ├── Core │ ├── CoreTypesTest.swift │ ├── FlowCryptCoreTests.swift │ ├── Models │ │ └── KeyDetailsTests.swift │ └── data.txt ├── ExtensionTests.swift ├── Extensions │ ├── StringTestExtension.swift │ └── XCTestCaseExtension.swift ├── Functionality │ ├── Apis │ │ ├── Account Server Apis │ │ │ └── EnterpriseServerApiTests.swift │ │ └── Remote Private Key Apis │ │ │ └── EmailKeyManagerApi.swift │ ├── FilesManager │ │ ├── FileMock.swift │ │ └── FilesManagerTests.swift │ ├── Mail Provider │ │ └── GmailServiceTest.swift │ ├── PGP │ │ ├── KeyMethodsTest.swift │ │ └── PasswordProtectedMsgTest.swift │ ├── Services │ │ ├── Client Configuration Service │ │ │ ├── ClientConfigurationServiceTests.swift │ │ │ ├── ClientConfigurationTests.swift │ │ │ └── Mocks │ │ │ │ ├── ClientConfigurationProviderMock.swift │ │ │ │ ├── CurrentUserEmailMock.swift │ │ │ │ ├── EnterpriseServerApiMock.swift │ │ │ │ └── OrganisationalRulesServiceMock.swift │ │ ├── Key Services │ │ │ ├── Models │ │ │ │ └── RecipientTests.swift │ │ │ └── PubLookupTest.swift │ │ └── PassPhraseStorageTests │ │ │ ├── InMemoryPassPhraseStorageTest.swift │ │ │ ├── PassPhraseStorageMock.swift │ │ │ └── PassPhraseStorageTests.swift │ └── WKDURLs │ │ ├── WKDURLsConstructorTests.swift │ │ └── ZBase32EncodingTests.swift ├── GeneralConstantsTest.swift ├── ImapHelperTest.swift ├── Info.plist ├── LocalStorageTests.swift ├── Mocks │ ├── CoreComposeMessageMock.swift │ ├── DraftGatewayMock.swift │ ├── EncryptedStorageMock.swift │ ├── LocalContactsProviderMock.swift │ ├── MessageGatewayMock.swift │ └── MockError.swift ├── Models Parsing │ ├── RawClientConfigurationTests.swift │ ├── client_configuraion.json │ ├── client_configuraion_empty.json │ ├── client_configuraion_partly_empty.json │ └── client_configuraion_with_unknown_flag.json ├── TestData.swift └── TestTimer.swift ├── FlowCryptCommon ├── Extensions │ ├── ASButtonNode.swift │ ├── BundleExtensions.swift │ ├── CalendarExtensions.swift │ ├── CollectionExtensions.swift │ ├── Data │ │ ├── DataExtensions+ZBase32Encoding.swift │ │ └── DataExtensions.swift │ ├── DateFormattingExtensions.swift │ ├── Either.swift │ ├── EncodableExtensions.swift │ ├── ErrorExtensions.swift │ ├── IntExtensions.swift │ ├── LocalizationExtensions.swift │ ├── NotificationExtension.swift │ ├── OptionalExtensions.swift │ ├── SequenceExtension.swift │ ├── StringExtensions.swift │ ├── StyleExtensions.swift │ ├── Task+Retry.swift │ ├── Then.swift │ ├── UIAlertControllerExtensions.swift │ ├── UIApplicationExtensions.swift │ ├── UIDeviceExtensions.swift │ ├── UIEdgeInsetsExtensions.swift │ ├── UIImageExtensions.swift │ ├── UIPopoverPresentationControllerExtensions.swift │ ├── UIViewControllerExtensions.swift │ ├── UIViewExtensions.swift │ ├── URLExtensions.swift │ └── URLSessionExtensions.swift ├── FlowCryptCommon.h ├── Info.plist ├── Logger.swift ├── TapTicFeedback.swift └── Trace.swift ├── FlowCryptUI ├── Cell Nodes │ ├── BackupCellNode.swift │ ├── ButtonCellNode.swift │ ├── CellNode.swift │ ├── CheckBoxTextNode.swift │ ├── ComposeRecipientCellNode.swift │ ├── ComposeRecipientPopupNameNode.swift │ ├── ContactAddNode.swift │ ├── ContactCellNode.swift │ ├── ContactKeyCellNode.swift │ ├── ContactUserCellNode.swift │ ├── DividerCellNode.swift │ ├── EmptyCellNode.swift │ ├── EmptyFolderCellNode.swift │ ├── InboxCellNode.swift │ ├── InfoCellNode.swift │ ├── LabelCellNode.swift │ ├── MenuSeparatorCellNode.swift │ ├── MessageActionCellNode.swift │ ├── MessageSubjectNode.swift │ ├── MessageTextSubjectNode.swift │ ├── RecipientEmailNode.swift │ ├── RecipientEmailTextFieldNode.swift │ ├── RecipientEmailsCellNode.swift │ ├── RecipientEmailsCellNodeInput.swift │ ├── RecipientFromCellNode.swift │ ├── SecurityWarningNode.swift │ ├── SetupTitleNode.swift │ ├── SwitchCellNode.swift │ ├── TextCellNode.swift │ ├── TextFieldCellNode.swift │ ├── TextViewCellNode.swift │ ├── ThreadDetailWebNode.swift │ ├── ThreadMessageDraftCellNode.swift │ ├── ThreadMessageInfoCellNode.swift │ └── TitleCellNode.swift ├── FlowCryptUI.h ├── Info.plist ├── Nodes │ ├── AddButtonNode.swift │ ├── AttachmentNode.swift │ ├── AvatarCheckboxNode.swift │ ├── BadgeNode.swift │ ├── ButtonNode.swift │ ├── ButtonWithPaddingNode.swift │ ├── CheckBoxNode.swift │ ├── CoreAlertNode.swift │ ├── CustomAlertNode.swift │ ├── KeySettingCellNode.swift │ ├── KeyTextCellNode.swift │ ├── LinkButtonNode.swift │ ├── MessageRecipientsNode.swift │ ├── PassPhraseAlertNode.swift │ ├── PgpOnlySwitchNode.swift │ ├── PublicKeyDetailNode.swift │ ├── SignInDescriptionNode.swift │ ├── SignInImageNode.swift │ ├── SigninButtonNode.swift │ ├── TableNode.swift │ ├── TableViewController.swift │ ├── TextFieldNode.swift │ ├── TextImageNode.swift │ ├── TextWithIconNode.swift │ ├── ToggleQuoteButtonNode.swift │ ├── ViewController.swift │ └── WebNode.swift ├── Resources │ └── Helpers.swift ├── UIConstants.swift └── Views │ ├── CheckBoxCircleView.swift │ ├── ENSideMenu.swift │ ├── ENSideMenuNavigationController.swift │ ├── LeftAlignedCollectionViewFlowLayout.swift │ ├── NavigationBarActionButton.swift │ ├── NavigationBarItemsView.swift │ ├── RecipientEmailCollectionViewFlowLayout.swift │ └── SideMenuOptionalView.swift ├── FlowCryptUIApplication ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Colors.xcassets │ ├── Contents.json │ ├── activityIndicatorColor.colorset │ │ └── Contents.json │ ├── additionalInfoLabelColor.colorset │ │ └── Contents.json │ ├── backgroundColor.colorset │ │ └── Contents.json │ ├── dividerColor.colorset │ │ └── Contents.json │ ├── mainGreenColor.colorset │ │ └── Contents.json │ ├── mainTextColor.colorset │ │ └── Contents.json │ └── mainTextUnreadColor.colorset │ │ └── Contents.json ├── Info.plist ├── README.md └── ViewController.swift ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── README.md ├── Scripts ├── generate-icons.sh └── generate-mock-cert.sh ├── appium ├── .eslintignore ├── .eslintrc.js ├── README.md ├── api-mocks │ ├── apis │ │ ├── attester │ │ │ └── attester-endpoints.ts │ │ ├── ekm │ │ │ └── ekm-endpoints.ts │ │ ├── fes │ │ │ └── fes-endpoints.ts │ │ ├── google │ │ │ ├── exported-messages │ │ │ │ ├── message-export-17963aa512ecfe35.json │ │ │ │ ├── message-export-17d48c645be70702.json │ │ │ │ ├── message-export-17d4c2e90b3e6194.json │ │ │ │ ├── message-export-17d4c2e9c60eaec1.json │ │ │ │ ├── message-export-17d4c2ea259f538f.json │ │ │ │ ├── message-export-17d4c2ea83754f3f.json │ │ │ │ ├── message-export-17d53806a583b63e.json │ │ │ │ ├── message-export-17d58920dc9e9ace.json │ │ │ │ ├── message-export-17d5d783858db670.json │ │ │ │ ├── message-export-17d6d04b4a2f42b5.json │ │ │ │ ├── message-export-17d71e6ea1e3ada1.json │ │ │ │ ├── message-export-17d7a76924f539ec.json │ │ │ │ ├── message-export-17d7e8e52ffd2b1f.json │ │ │ │ ├── message-export-17d7fd72ba9707ad.json │ │ │ │ ├── message-export-17ec4013e9b497e4.json │ │ │ │ ├── message-export-17ed2f87eae41645.json │ │ │ │ ├── message-export-17ed84b44097b43d.json │ │ │ │ ├── message-export-180b3b5efbdc46da.json │ │ │ │ ├── message-export-180b3b8f20d6cbd5.json │ │ │ │ ├── message-export-180f7584791db3ef.json │ │ │ │ ├── message-export-18191e1cab3ca811.json │ │ │ │ ├── message-export-181d8abb1593af88.json │ │ │ │ ├── message-export-18244f525d7e0023.json │ │ │ │ ├── message-export-18244f5606e98653.json │ │ │ │ ├── message-export-18244f5e83a4a7aa.json │ │ │ │ ├── message-export-183f03ca7d23ca7c.json │ │ │ │ ├── message-export-18480655babd6c07.json │ │ │ │ ├── message-export-18481ad047f62d2a.json │ │ │ │ ├── message-export-1848c0fe74b53331.json │ │ │ │ ├── message-export-185df3eb8446cadb.json │ │ │ │ ├── message-export-1864b9cbb8bd58ad.json │ │ │ │ ├── message-export-18a8888351cc96c3.json │ │ │ │ ├── message-export-18a88891abaf2322.json │ │ │ │ ├── message-export-18b8f52368f8bfdf.json │ │ │ │ ├── message-export-18f5b8fe65b3c1cf.json │ │ │ │ ├── message-export-1922cd70bf323ea6.json │ │ │ │ ├── message-export-192d58178bf2ca46.json │ │ │ │ └── message-export-1954b6e687bded15.json │ │ │ ├── google-data.ts │ │ │ ├── google-endpoints.ts │ │ │ └── google-messages.ts │ │ └── wkd │ │ │ └── wkd-endpoints.ts │ ├── core │ │ ├── attachment.ts │ │ ├── buf.ts │ │ ├── common.ts │ │ ├── const.ts │ │ ├── crypto │ │ │ ├── key.ts │ │ │ ├── pgp │ │ │ │ ├── msg-util.ts │ │ │ │ ├── openpgp-key.ts │ │ │ │ ├── openpgpjs-custom.ts │ │ │ │ ├── pgp-armor.ts │ │ │ │ ├── pgp-hash.ts │ │ │ │ └── pgp-password.ts │ │ │ └── smime │ │ │ │ └── smime-key.ts │ │ ├── mime.ts │ │ ├── msg-block-parser.ts │ │ ├── msg-block.ts │ │ ├── stream.ts │ │ └── types │ │ │ ├── emailjs.d.ts │ │ │ └── openpgp.d.ts │ ├── lib │ │ ├── api.ts │ │ ├── configuration-types.ts │ │ ├── mock-util.ts │ │ ├── oauth.ts │ │ └── openpgp.d.ts │ ├── mock-config.ts │ ├── mock-data.ts │ ├── mock-ssl-cert │ │ ├── cert.pem.mock │ │ └── key.pem.mock │ ├── mock.ts │ ├── platform │ │ ├── catch.ts │ │ ├── debug.ts │ │ ├── key-cache.ts │ │ ├── require.ts │ │ ├── store │ │ │ └── contact-store.ts │ │ ├── util.ts │ │ └── xss.ts │ └── util │ │ └── parse.ts ├── config │ ├── wdio.live.conf.ts │ ├── wdio.mock.conf.ts │ └── wdio.shared.conf.ts ├── package-lock.json ├── package.json ├── tests │ ├── constants.ts │ ├── data │ │ └── index.ts │ ├── helpers │ │ ├── AppiumHelper.ts │ │ ├── DataHelper.ts │ │ ├── ElementHelper.ts │ │ ├── MailFolderHelper.ts │ │ ├── PublicKeyHelper.ts │ │ ├── TouchHelper.ts │ │ └── WebView.ts │ ├── screenobjects │ │ ├── add-contact.screen.ts │ │ ├── all-screens.ts │ │ ├── attachment.screen.ts │ │ ├── base.screen.ts │ │ ├── contact-public-key.screen.ts │ │ ├── contacts.screen.ts │ │ ├── email-provider.screen.ts │ │ ├── email.screen.ts │ │ ├── keys.screen.ts │ │ ├── mail-folder.screen.ts │ │ ├── menu-bar.screen.ts │ │ ├── new-message.screen.ts │ │ ├── public-key-details.screen.ts │ │ ├── public-key.screen.ts │ │ ├── refresh-key.screen.ts │ │ ├── search.screen.ts │ │ ├── settings.screen.ts │ │ ├── setup-key.screen.ts │ │ └── splash.screen.ts │ ├── specs │ │ ├── live │ │ │ └── update │ │ │ │ └── CheckAppAfterUpdateFromOldVersion.spec.ts │ │ └── mock │ │ │ ├── composeEmail │ │ │ ├── CheckAttachmentAfterForward.spec.ts │ │ │ ├── CheckCaretForReplyAndForward.spec.ts │ │ │ ├── CheckComposeEmailAfterReopening.spec.ts │ │ │ ├── CheckDraftsFunctionality.spec.ts │ │ │ ├── CheckEKMRefreshAfterComposeError.spec.ts │ │ │ ├── CheckEmailSignature.spec.ts │ │ │ ├── CheckEmptyAllowAttesterSearchOnlyForDomains.spec.ts │ │ │ ├── CheckForwardMessageWithAttachedPubKey.spec.ts │ │ │ ├── CheckInvalidEmailRecipient.spec.ts │ │ │ ├── CheckPasswordMessageCompliance.spec.ts │ │ │ ├── CheckPasswordProtectedMessageBccLeak.spec.ts │ │ │ ├── CheckRecipientColorAfterRemovingPubKey.spec.ts │ │ │ ├── CheckRecipientEvaluationWhenTapAway.spec.ts │ │ │ ├── CheckRecipientPopup.spec.ts │ │ │ ├── CheckSendAsAlias.spec.ts │ │ │ ├── CheckSignOnlyKey.spec.ts │ │ │ ├── RecipientListLabelCheck.spec.ts │ │ │ ├── SelectRecipientByName.spec.ts │ │ │ ├── SendEmailToRecipientWithExpiredAndRevokedPublicKeys.spec.ts │ │ │ ├── SendEmailToRecipientWithoutPublicKey.spec.ts │ │ │ └── SendEncryptedEmailAfterPassPhraseSessionEndedAndTrashIt.spec.ts │ │ │ ├── inbox │ │ │ ├── CheckAllEmailSignatureCases.spec.ts │ │ │ ├── CheckArchiveAndMoveToInbox.spec.ts │ │ │ ├── CheckArchiveThreadTooAggressive.spec.ts │ │ │ ├── CheckCanaryMail.spec.ts │ │ │ ├── CheckEmailWithAttachmentSentViaEncryptedContactPage.spec.ts │ │ │ ├── CheckEmptyTrash.spec.ts │ │ │ ├── CheckEncryptedEmailAfterRestartApp.spec.ts │ │ │ ├── CheckMessageProcessingErrors.spec.ts │ │ │ ├── CheckPlainMessageQuote.spec.ts │ │ │ ├── CheckRemoteImageRendering.spec.ts │ │ │ ├── CheckReplyAndForwardForEncryptedEmail.spec.ts │ │ │ ├── CheckReplyForReplyToHeader.spec.ts │ │ │ ├── CheckReplyMessageWithChangingRecipient.spec.ts │ │ │ ├── CheckRichTextEmailRendering.spec.ts │ │ │ ├── CheckShowOnlyEncryptedEmails.spec.ts │ │ │ ├── CheckSwipeActions.spec.ts │ │ │ ├── CheckThreadRendering.spec.ts │ │ │ ├── CheckThreadSelectFeature.spec.ts │ │ │ ├── ImportPublicKeyReceivedByEmail.spec.ts │ │ │ ├── ReadAttachmentEmail.spec.ts │ │ │ ├── ReadEmailAfterRestartApp.spec.ts │ │ │ └── ReadTextEmail.spec.ts │ │ │ ├── login │ │ │ ├── CancelLoginAndLogout.spec.ts │ │ │ └── LoginToMultipleAccounts.spec.ts │ │ │ ├── settings │ │ │ ├── CheckImportPublicKeyFromContacts.spec.ts │ │ │ ├── CheckRemovingContacts.spec.ts │ │ │ ├── CheckSettingsForLoggedUser.spec.ts │ │ │ ├── CheckUpdatingKeysFromWKD.spec.ts │ │ │ └── RespectsHideArmorMeta.spec.ts │ │ │ └── setup │ │ │ ├── CannotFindEmailOnAttesterWithDisallowAttesterSearchForDomain.spec.ts │ │ │ ├── CheckAntiBruteForceProtection.spec.ts │ │ │ ├── CheckDecryptMessageWhenNoKeys.spec.ts │ │ │ ├── CheckRecipientsInOfflineMode.spec.ts │ │ │ ├── CheckRefreshKeyFromEkmErrorHandling.spec.ts │ │ │ ├── CheckRefreshKeyFromEkmKeyDeletionAndRevokedKey.spec.ts │ │ │ ├── CheckRefreshKeyFromEkmNoLocalKeys.spec.ts │ │ │ ├── CheckRefreshKeyFromEkmWithPassPhrasePrompt.spec.ts │ │ │ ├── CheckRefreshKeyFromEkmWithoutPassPhrasePrompt.spec.ts │ │ │ ├── CheckRevokedPublicKeyUpdate.spec.ts │ │ │ ├── FindEmailOnAttester.spec.ts │ │ │ ├── FindEmailOnWkd.spec.ts │ │ │ ├── PrefersWkdKeysOverAttester.spec.ts │ │ │ ├── RespectsAllowAttesterSearchOnlyForDomains.spec.ts │ │ │ ├── RespectsDisallowAttesterSearchForDomainsOnPerDomainBasic.spec.ts │ │ │ ├── RespectsPassPhraseSessionLength.spec.ts │ │ │ ├── SetupFailsWithInconsistentClientConfiguration.spec.ts │ │ │ ├── SetupOnlyRevokedKeyFromEKM.spec.ts │ │ │ ├── SetupRevokedAndValidKeysFromEKM.spec.ts │ │ │ ├── SetupShowsMeaningfulErrorFromFES.spec.ts │ │ │ ├── SetupWithoutFesWhenFesReturnsError.spec.ts │ │ │ └── UpdateOlderPubKeyToNewer.spec.ts │ └── types │ │ └── node-forge.d.ts ├── tsconfig.eslint.json └── tsconfig.json ├── asset-sources └── icons │ ├── flowcrypt-ios-debug-svg.svg │ ├── flowcrypt-ios-enterprise-svg.svg │ └── flowcrypt-ios-svg.svg ├── code-design.md ├── docker-mailserver ├── .env ├── check_email_server.sh ├── config │ ├── dovecot-quotas.cf │ ├── dovecot.cf │ ├── postfix-accounts.cf │ ├── postfix-aliases.cf │ └── ssl │ │ ├── mail.flowcrypt.test-cert.pem │ │ ├── mail.flowcrypt.test-combined.pem │ │ ├── mail.flowcrypt.test-key.pem │ │ └── mail.flowcrypt.test-req.pem ├── docker-compose.yml ├── env-mailserver ├── restart_email_server.sh ├── run_email_server.sh └── stop_email_server.sh ├── fastlane ├── Appfile ├── Fastfile └── Snapfile ├── flowcrypt-ios.code-workspace ├── package-lock.json └── package.json /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj merge=union 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sosnovsky 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | This PR 2 | 3 | close #0000 // if this PR closes an issue 4 | 5 | issue #0000 // if it doesn't close the issue yet 6 | 7 | ---------------------------------- 8 | 9 | **Tests** _(delete all except exactly one)_: 10 | - Does not need tests (refactor only, docs or internal changes) 11 | - Difficult to test (explain why) 12 | - Not worth testing 13 | - Tests will be added later (issue #...) 14 | - Tests added or updated 15 | 16 | -------------------------------- 17 | 18 | ### To be filled by reviewers 19 | 20 | I have reviewed that this PR... _(tick whichever items you personally focused on during this review)_: 21 | - [ ] addresses the issue it closes (if any) 22 | - [ ] code is readable and understandable 23 | - [ ] is accompanied with tests, or tests are not needed 24 | - [ ] is free of vulnerabilities 25 | - [ ] is documented clearly and usefully, or doesn't need documentation 26 | -------------------------------------------------------------------------------- /.github/workflows/check-for-emails.yml: -------------------------------------------------------------------------------- 1 | name: check-for-emails 2 | on: 3 | issue_comment: 4 | types: [created, edited] 5 | issues: 6 | types: [opened, edited] 7 | jobs: 8 | find_emails: 9 | runs-on: ubuntu-latest 10 | name: Check for emails in issue comments 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Scan comment 15 | id: scan 16 | uses: FlowCrypt/comment-email-address-alerts@v21 17 | with: 18 | repo-token: ${{ secrets.GITHUB_TOKEN }} 19 | exemptions: test,example.com,flowcrypt.com 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "31 18 * * 4" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ javascript ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v3 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v3 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v3 40 | with: 41 | category: "/language:${{ matrix.language }}" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | Pods/ 3 | *.xcworkspacedata 4 | *.xcuserstate 5 | xcschememanagement.plist 6 | *.xcbkptlist 7 | cmake-build-debug/ 8 | xcuserdata/ 9 | 10 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 11 | *.pbxuser 12 | *.mode1v3 13 | *.mode2v3 14 | *.perspectivev3 15 | *.xcuserstate 16 | !default.pbxuser 17 | !default.mode1v3 18 | !default.mode2v3 19 | !default.perspectivev3 20 | 21 | 22 | # OS X temporary files 23 | *.DS_Store 24 | *.swp 25 | *.lock 26 | profile 27 | .nib 28 | DerivedData/ 29 | Builds/ 30 | vendor/ 31 | 32 | 33 | .idea/workspace.xml 34 | .idea 35 | 36 | # fastlane 37 | 38 | fastlane/report.xml 39 | fastlane/Preview.html 40 | fastlane/screenshots/**/*.png 41 | fastlane/test_output 42 | fastlane/README.md 43 | 44 | test-ci-secrets.json 45 | 46 | node_modules 47 | 48 | # appium 49 | appium/node_modules 50 | appium/tmp 51 | appium/.env 52 | appium/FlowCrypt.app 53 | appium/WebDriverAgentRunner-Runner.app 54 | appium/FlowCryptOld.app 55 | appium/video 56 | 57 | Scripts/gmp -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | npx git-format-staged --formatter "swiftformat stdin --stdinpath '{}'" "*.swift" -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | *.yml 3 | *.code-workspace 4 | Core/source/lib/* -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "singleQuote": true, 5 | "endOfLine": "auto", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.9 2 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --disable blankLinesAroundMark 2 | --disable blankLinesAtStartOfScope 3 | --disable redundantSelf 4 | --disable redundantReturn 5 | --disable trailingCommas 6 | --disable unusedArguments 7 | --disable wrapMultilineStatementBraces 8 | 9 | --exclude appium,Core,fastlane,Pods,vendor -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "appium": true, 4 | "Core": true 5 | }, 6 | } -------------------------------------------------------------------------------- /Core/.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | source/core/types 3 | tooling -------------------------------------------------------------------------------- /Core/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | build/* 3 | tmp/* 4 | node_modules 5 | /test.sh 6 | -------------------------------------------------------------------------------- /Core/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnPaste": true, 4 | "eslint.validate": ["javascript", "typescript"], 5 | "editor.rulers": [80, 100, 120] 6 | } 7 | -------------------------------------------------------------------------------- /Core/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "test", 9 | "path": ".", 10 | "problemMatcher": [] 11 | }, 12 | { 13 | "type": "npm", 14 | "script": "updateCore", 15 | "path": ".", 16 | "problemMatcher": [] 17 | }, 18 | { 19 | "type": "npm", 20 | "script": "donateCore", 21 | "path": ".", 22 | "problemMatcher": [] 23 | }, 24 | { 25 | "type": "npm", 26 | "script": "deploy", 27 | "path": ".", 28 | "problemMatcher": [] 29 | }, 30 | { 31 | "type": "npm", 32 | "script": "genCompatAssets", 33 | "path": ".", 34 | "problemMatcher": [] 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /Core/source/assets/compat/direct-encrypted-text-special-chars.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP MESSAGE----- 2 | Version: FlowCrypt [BUILD_REPLACEABLE_VERSION] Gmail Encryption 3 | Comment: Seamlessly send and receive encrypted email 4 | 5 | wcBMAwurnAGLJl0iAQf/VynWdf/qkF1Wx6icj5OwXPYS3pGuupqQfkEh6ioO 6 | Z67mPkhNHqw9ws9zYR1OAiDBxe5lYEasxTaD/Pte4ksaavHiRScQbdHpw7bj 7 | 2xjuud4gmbQAQarUvJ2j/gFYYSqvcenYsoZvgMqwyLMvVdfSreyAI8P11+U+ 8 | /qKr+spRsIS+QOYAzuuhZQv1UilzuGMTKwMwF8SPPZFKMr1m68Lm8aeQnYsV 9 | pGkdEzHQ2Ya0ULSsuk2m8UdTR+wjBiHn8ApxZlyVve/HkGRW56leGEGF2Sxj 10 | N3s1UF39drskXqT8WJge6GaJvt2SSF5EpEeiGwtkKjHHG6YSIRxzgIkdzVoU 11 | xdJdAe5bFGYt6+Vm4PflIPqFFUltnAU6B+Xyl2Aa/nqCL/gAKVhvhU43hQ14 12 | A50CdV6kJCZVJxXOn6oatQvHX3FqaMihvDMpHlfJgQHpt+3Kf/KEPDpqjP0S 13 | IgpZ1GmF 14 | =dGLC 15 | -----END PGP MESSAGE----- 16 | -------------------------------------------------------------------------------- /Core/source/assets/compat/direct-encrypted-text.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP MESSAGE----- 2 | Version: FlowCrypt [BUILD_REPLACEABLE_VERSION] Gmail Encryption 3 | Comment: Seamlessly send and receive encrypted email 4 | 5 | wcBMAwurnAGLJl0iAQf+I2exIah3XL/zfPozDmVFSLJk4tBFIlIyFfGYcw5W 6 | +ebOL3Gu/+/oCIIlXrdP0FxIVEYnSEaevmB9p0FfXGpcw4Wr8PBnSubCkn2s 7 | +V//k6W1Uu915GmiwCgDkLTCP7vEHvwUglNvgAatDtNdJ3xrf2gjOOFiYQnn 8 | 4JSI1msMfL5tmdFCyXm1g4mUe9MdVXfphrXIyvGu1Sufhv+T5FgteDW0c6lM 9 | g7G6jgX4q5xiT8r2LTxKlxHVlQSqvGlnx/yRXwqBs3PAMiS4u5JlKJX4aKVy 10 | FyN+gq++tWZC1XCSFzXfAf0rXcoDZ7nEkxdkKQqXgA6LCsFD79FMCtuenvzU 11 | U9JEAdvmmpGlextZcfCUmGgclQXgowDnjaXy5Uc6Bzmi8AlY/4MFo0Q3bOU4 12 | kNhLCiXTGNJlFDd0HLz8Cy7YXzLWZ94IuGk= 13 | =Bvit 14 | -----END PGP MESSAGE----- 15 | -------------------------------------------------------------------------------- /Core/source/assets/compat/mime-email-encrypted-inline-text.txt: -------------------------------------------------------------------------------- 1 | Delivered-To: flowcrypt.compatibility@gmail.com 2 | Return-Path: 3 | Openpgp: id=6D24791A5B106262B06217C606CA553EC2455D70 4 | From: cryptup.tester@gmail.com 5 | MIME-Version: 1.0 6 | Date: Thu, 2 Nov 2017 17:54:14 -0700 7 | Message-ID: 8 | Subject: mime email encrypted inline text 9 | To: flowcrypt.compatibility@gmail.com 10 | Content-Type: text/plain; charset="UTF-8" 11 | 12 | -----BEGIN PGP MESSAGE----- 13 | Version: FlowCrypt [BUILD_REPLACEABLE_VERSION] Gmail Encryption 14 | Comment: Seamlessly send and receive encrypted email 15 | 16 | wcBMAwurnAGLJl0iAQf/SSqySs436k8S6wRV84vaRMyb1NeYyqgvspbjznqG 17 | d7jn90cVgJeHDz+Bmsqxx8jI8xKebiEjqv5F8H7jX7QGUsRkdfPv0gn94gHI 18 | A1Z/N5TNc/dhP0zRd/NG6ZNwYjOv6m5Reyu3kcNcXEA97zA5O0QkDgOkLm/L 19 | ybfNvH5qtVpSq3pLGuthQLeQ+GGkNMrynNyFHULbK9O+MNJHK8GUxRdDjsyh 20 | GoxM6BxhLDnHqbcG3abdBx3xPt1kLLF/xQ1Sipic36pyFGKw2mgFu44/cLEz 21 | ZTZv3wuq0Zj+sKBB1iOJJRAjhJzmJFt/MChpsExdTHZAeNdGDOqDmr1qXxFa 22 | B9JEAS8glDxX7oXijWFm8wSjQymZwaVuLZar43Yy4k6CxvzxxOlxapE1dKUP 23 | hC84J/ie8GjYxysDp8Tqtv1HRzozaC/aZsg= 24 | =E57W 25 | -----END PGP MESSAGE----- 26 | 27 | -------------------------------------------------------------------------------- /Core/source/assets/compat/mime-email-plain-html.txt: -------------------------------------------------------------------------------- 1 | Delivered-To: flowcrypt.compatibility@gmail.com 2 | Message-ID: <1760895073.1552049750035.JavaMail.dets@ny-dets-001> 3 | Date: Fri, 8 Mar 2019 07:55:50 -0500 (EST) 4 | From: cryptup.tester@gmail.com 5 | To: flowcrypt.compatibility@gmail.com 6 | Subject: mime email plain html 7 | Mime-Version: 1.0 8 | Content-Type: text/html; charset=utf-8 9 | Content-Transfer-Encoding: quoted-printable 10 | 11 |

paragraph 1

paragraph 2 with bold

par= 12 | agraph 3 with red i

13 | -------------------------------------------------------------------------------- /Core/source/assets/compat/mime-email-plain-signed-edited.txt: -------------------------------------------------------------------------------- 1 | Delivered-To: flowcrypt.compatibility@gmail.com 2 | Return-Path: 3 | Openpgp: id=E76853E128A0D376CAE47C143A30F4CC0A9A8F10 4 | From: flowcrypt.compatibility@gmail.com 5 | MIME-Version: 1.0 6 | Date: Thu, 2 Nov 2017 17:54:14 -0700 7 | Message-ID: 8 | Subject: mime email plain signed 9 | To: flowcrypt.compatibility@gmail.com 10 | Content-Type: text/plain; charset="UTF-8" 11 | 12 | 13 | -----BEGIN PGP SIGNED MESSAGE----- 14 | Hash: SHA256 15 | 16 | some 17 | 汉 18 | TXT 19 | -----BEGIN PGP SIGNATURE----- 20 | Version: FlowCrypt [BUILD_REPLACEABLE_VERSION] Gmail Encryption 21 | Comment: Seamlessly send and receive encrypted email 22 | 23 | wsBcBAEBCAAGBQJhY1x0AAoJEDow9MwKmo8QMR0H/iHkHFr1doJhqIVsSXyQ 24 | bBwYpoaIRHEZlF3RQa3sbkLqXSTzHf81ySb7/oXgd+z5RYYw2vD9bebaInb3 25 | Ca9FuBrM4ZlnmVVnNpv/nntZZdln+AVq+rEOaLDcHC4nFTC3Z7QwulZn31Hm 26 | b0JcsGAuIDfUZ1mFfFpyCq7KB+XGbV3bc+bUBO0pjKfbsEISBqJxuCzJIr+0 27 | pIwMSdluQXxtyyqT3aXnoH9jl75dBDDvMEPR3c/I2+k9SWWoYslwhn2TqB94 28 | RFgwWpw/SH8+1b3lI1AWnkVpkkm8JOnteViHCz85ALd9xkACjq8UavPGLy0v 29 | J6XRgsV1E2RTmydFB6vv/9w= 30 | =quFc 31 | -----END PGP SIGNATURE----- 32 | 33 | -------------------------------------------------------------------------------- /Core/source/assets/compat/mime-email-plain-signed.txt: -------------------------------------------------------------------------------- 1 | Delivered-To: flowcrypt.compatibility@gmail.com 2 | Return-Path: 3 | Openpgp: id=E76853E128A0D376CAE47C143A30F4CC0A9A8F10 4 | From: flowcrypt.compatibility@gmail.com 5 | MIME-Version: 1.0 6 | Date: Thu, 2 Nov 2017 17:54:14 -0700 7 | Message-ID: 8 | Subject: mime email plain signed 9 | To: flowcrypt.compatibility@gmail.com 10 | Content-Type: text/plain; charset="UTF-8" 11 | 12 | 13 | -----BEGIN PGP SIGNED MESSAGE----- 14 | Hash: SHA256 15 | 16 | some 17 | 汉 18 | txt 19 | -----BEGIN PGP SIGNATURE----- 20 | Version: FlowCrypt [BUILD_REPLACEABLE_VERSION] Gmail Encryption 21 | Comment: Seamlessly send and receive encrypted email 22 | 23 | wsBcBAEBCAAGBQJhY1x0AAoJEDow9MwKmo8QMR0H/iHkHFr1doJhqIVsSXyQ 24 | bBwYpoaIRHEZlF3RQa3sbkLqXSTzHf81ySb7/oXgd+z5RYYw2vD9bebaInb3 25 | Ca9FuBrM4ZlnmVVnNpv/nntZZdln+AVq+rEOaLDcHC4nFTC3Z7QwulZn31Hm 26 | b0JcsGAuIDfUZ1mFfFpyCq7KB+XGbV3bc+bUBO0pjKfbsEISBqJxuCzJIr+0 27 | pIwMSdluQXxtyyqT3aXnoH9jl75dBDDvMEPR3c/I2+k9SWWoYslwhn2TqB94 28 | RFgwWpw/SH8+1b3lI1AWnkVpkkm8JOnteViHCz85ALd9xkACjq8UavPGLy0v 29 | J6XRgsV1E2RTmydFB6vv/9w= 30 | =quFc 31 | -----END PGP SIGNATURE----- 32 | 33 | -------------------------------------------------------------------------------- /Core/source/assets/compat/mime-email-plain-with-attachment.txt: -------------------------------------------------------------------------------- 1 | MIME-Version: 1.0 2 | Date: Sun, 13 Jun 2021 02:28:32 +0800 3 | Message-ID: 4 | Subject: plain message with attachment 5 | From: Tom 6 | To: "Tom J. H." 7 | Content-Type: multipart/mixed; boundary="000000000000b5c6c605c495ca81" 8 | 9 | --000000000000b5c6c605c495ca81 10 | Content-Type: multipart/alternative; boundary="000000000000b5c6c305c495ca7f" 11 | 12 | --000000000000b5c6c305c495ca7f 13 | Content-Type: text/plain; charset="UTF-8" 14 | Content-Transfer-Encoding: quoted-printable 15 | 16 | some 17 | =E6=B1=89 18 | txt 19 | 20 | --000000000000b5c6c305c495ca7f 21 | Content-Type: text/html; charset="UTF-8" 22 | Content-Transfer-Encoding: quoted-printable 23 | 24 | some
=E6=B1=89
txt 25 | 26 | --000000000000b5c6c305c495ca7f-- 27 | --000000000000b5c6c605c495ca81 28 | Content-Type: text/plain; charset="US-ASCII"; name="name.txt" 29 | Content-Disposition: attachment; filename="name.txt" 30 | Content-Transfer-Encoding: base64 31 | X-Attachment-Id: f_kpu3dty00 32 | Content-ID: 33 | 34 | ZmlsZSBjb250ZW50IGhlcmUK 35 | --000000000000b5c6c605c495ca81-- -------------------------------------------------------------------------------- /Core/source/assets/compat/mime-email-plain.txt: -------------------------------------------------------------------------------- 1 | Delivered-To: flowcrypt.compatibility@gmail.com 2 | Return-Path: 3 | Openpgp: id=6D24791A5B106262B06217C606CA553EC2455D70 4 | From: cryptup.tester@gmail.com 5 | MIME-Version: 1.0 6 | Date: Thu, 2 Nov 2017 17:54:14 -0700 7 | Message-ID: 8 | Subject: mime email plain 9 | To: flowcrypt.compatibility@gmail.com 10 | Content-Type: text/plain; charset="UTF-8" 11 | 12 | some 13 | 汉 14 | txt 15 | -------------------------------------------------------------------------------- /Core/source/core/const.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | 'use strict'; 4 | 5 | export const VERSION = '[BUILD_REPLACEABLE_VERSION]'; 6 | export const GOOGLE_API_HOST = '[BUILD_REPLACEABLE_GOOGLE_API_HOST]'; 7 | export const GOOGLE_OAUTH_SCREEN_HOST = '[BUILD_REPLACEABLE_GOOGLE_OAUTH_SCREEN_HOST]'; 8 | export const GOOGLE_CONTACTS_API_HOST = '[BUILD_REPLACEABLE_GOOGLE_CONTACTS_API_HOST]'; 9 | export const BACKEND_API_HOST = '[BUILD_REPLACEABLE_BACKEND_API_HOST]'; 10 | 11 | /** 12 | * Only put constants below if: 13 | * - they are useful across web/extension/Nodejs environments, AND 14 | * - the only other reasonable place to put them would be OUTSIDE of the /core folder 15 | * - example: A Google query below would normally go in Google class, 16 | * but that's outside of /core and we also need it on Android 17 | * 18 | * For any constants that are not expected to be reused that widely, 19 | * just put them as private or public static props in relevant class. 20 | */ 21 | 22 | export const GMAIL_RECOVERY_EMAIL_SUBJECTS = [ 23 | 'Your FlowCrypt Backup', 24 | 'Your CryptUp Backup', 25 | 'All you need to know about CryptUP (contains a backup)', 26 | 'CryptUP Account Backup', 27 | ]; 28 | -------------------------------------------------------------------------------- /Core/source/core/types/emailjs.d.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | export class MimeParser { 3 | public onheader: (node: MimeParserNode) => void; 4 | public onbody: (node: MimeParserNode) => void; 5 | public onend: () => void; 6 | public node: MimeParserNode; // root node 7 | public write: (chunk: Uint8Array | string) => void; 8 | public end: () => void; 9 | public constructor(); 10 | } 11 | 12 | export type MimeParserNode = { 13 | path: string[]; 14 | headers: { 15 | [key: string]: { 16 | value: string; 17 | initial: string; 18 | params?: { charset?: string; filename?: string; name?: string }; 19 | }[]; 20 | }; 21 | rawContent: string | undefined; // only for content nodes (leaves) 22 | content: Uint8Array; 23 | appendChild: (child: MimeParserNode) => void; 24 | contentTransferEncoding: { value: string }; 25 | charset?: string; 26 | addHeader: (name: string, value: string) => void; 27 | raw: string; // on all nodes, not just body nodes 28 | _parentNode: MimeParserNode | null; 29 | _childNodes: MimeParserNode[] | false; 30 | _isMultipart: 'signed' | 'mixed' | false; 31 | _lineCount: number; 32 | _isRfc822: boolean; 33 | _multipartBoundary: string | false; 34 | }; 35 | -------------------------------------------------------------------------------- /Core/source/entrypoint-bare.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | 'use strict'; 4 | 5 | import { Endpoints } from './mobile-interface/endpoints'; 6 | import { fmtErr } from './mobile-interface/format-output'; 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | declare const global: any; 10 | 11 | global.handleRequestFromHost = async (endpointName: string, request: string, data: Uint8Array) => { 12 | const endpoints = new Endpoints(); 13 | try { 14 | const handler = endpoints[endpointName]; 15 | if (!handler) { 16 | return fmtErr(new Error(`Unknown endpoint: ${endpointName}`)); 17 | } else { 18 | return handler(request, [data]) 19 | .then(res => res) 20 | .catch(err => fmtErr(err as Error)); 21 | } 22 | } catch (err) { 23 | return fmtErr(err as Error); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /Core/source/platform/catch.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | 'use strict'; 4 | 5 | export class Catch { 6 | public static reportErr = (e: Error) => { 7 | console.error(e); // core errors that were not re-thrown are not so interesting as of 2018 8 | }; 9 | 10 | public static report = (name: string, details?: unknown) => { 11 | console.error(name, details); // core reports are not so interesting as of 2018 12 | }; 13 | 14 | public static undefinedOnException = async (p: Promise): Promise => { 15 | try { 16 | return await p; 17 | } catch (e) { 18 | return undefined; 19 | } 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /Core/tooling/build-final.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const path = { 4 | bareDepsBundle: 'build/bundles/bare-deps-bundle.js', 5 | bareEntrypointBundle: 'build/bundles/entrypoint-bare-bundle.js', 6 | finalIos: 'build/final/flowcrypt-ios-prod.js', 7 | }; 8 | 9 | // bare 10 | const bareDepsSrc = fs.readFileSync(path.bareDepsBundle).toString(); 11 | const bareEntrypointSrc = fs 12 | .readFileSync(path.bareEntrypointBundle) 13 | .toString() 14 | .replace('"[BUILD_REPLACEABLE_VERSION]"', 'APP_VERSION'); 15 | 16 | // final (node, bare, dev) 17 | const finalBareSrc = ` 18 | let global = {}; 19 | let _log = (x) => window.webkit.messageHandlers.coreHost.postMessage({ name: "log", message: String(x)}); 20 | const console = { log: _log, error: _log, info: _log, warn: _log }; 21 | try { 22 | const module = {}; 23 | ${bareDepsSrc} 24 | /* entrypoint-bare starts here */ 25 | ${bareEntrypointSrc} 26 | /* entrypoint-bare ends here */ 27 | } catch(e) { 28 | console.error(e instanceof Error ? \`\${e.message}\\n\${(e.stack || 'no stack') 29 | .split("\\n") 30 | .map(l => " -> js " + l).join("\\n")}\` : e); 31 | throw e; 32 | } 33 | `; 34 | 35 | fs.writeFileSync(path.finalIos, finalBareSrc); 36 | -------------------------------------------------------------------------------- /Core/tooling/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | # clean up 6 | rm -rf ./build/ts ./build/bundles ./build/final/* 7 | mkdir -p ./build/final 8 | 9 | # build our source with typescript 10 | ./node_modules/.bin/tsc --project tsconfig.json 11 | 12 | # build raw/ with webpack 13 | ./node_modules/.bin/webpack --config webpack.bare.config.js 14 | 15 | # move modified raw/ to bundles/ 16 | node ./tooling/fix-bundles.js 17 | 18 | # concatenate external deps into one bundle 19 | ( cd ./build/bundles && cat bare-emailjs-bundle.js bare-zxcvbn-bundle.js > bare-deps-bundle.js ) # bare deps 20 | 21 | # create final builds for ios 22 | node ./tooling/build-final.js -------------------------------------------------------------------------------- /Core/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["./**/*.ts", "./*.js", "./tooling/*.js"] 4 | } 5 | -------------------------------------------------------------------------------- /Core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "commonjs", 5 | "lib": ["es2022", "dom"], 6 | "forceConsistentCasingInFileNames": true, 7 | "noImplicitReturns": true, 8 | "noUnusedLocals": true, 9 | "alwaysStrict": true, 10 | "noImplicitAny": true, 11 | "removeComments": true, 12 | "strictNullChecks": true, 13 | "noImplicitThis": true, 14 | "strictPropertyInitialization": false, 15 | "strictFunctionTypes": false, 16 | "sourceMap": false, 17 | "checkJs": false, 18 | "outDir": "./build/ts", 19 | "baseUrl": "./", 20 | "traceResolution": false, 21 | "typeRoots": ["./source/core/types/", "./node_modules/@types/"] 22 | }, 23 | "include": ["./source/**/*.ts"], 24 | "exclude": ["**/build/**", "**/node_modules/**"] 25 | } 26 | -------------------------------------------------------------------------------- /FlowCrypt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlowCrypt.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | 7 | // ___FILENAME___ 8 | // ___PACKAGENAME___ 9 | // 10 | // Created by ___FULLUSERNAME___ on ___DATE___ 11 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 12 | // 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/100.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/1024.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/114.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/120.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/144.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/152.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/167.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/180.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/20.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/29.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/40.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/50.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/57.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/58.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/60.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/72.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/76.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/80.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Debug.appiconset/87.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/100.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/1024.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/114.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/120.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/144.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/152.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/167.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/180.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/20.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/29.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/40.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/50.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/57.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/58.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/60.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/72.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/76.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/80.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-Enterprise.appiconset/87.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/100.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/1024.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/114.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/120.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/144.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/152.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/167.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/180.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/20.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/29.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/40.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/50.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/57.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/58.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/60.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/72.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/76.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/80.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon-TestFlight.appiconset/87.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/arrow_down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "arrow_down.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/arrow_down.imageset/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/arrow_down.imageset/arrow_down.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/arrow_up.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "arrow_up.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/arrow_up.imageset/arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/arrow_up.imageset/arrow_up.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/cancel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "cancel.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/cancel.imageset/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/cancel.imageset/cancel.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/copy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "copy.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/copy.imageset/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/copy.imageset/copy.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/download.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "download-2-5.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "download-2-6.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "download-2-7.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/download.imageset/download-2-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/download.imageset/download-2-5.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/download.imageset/download-2-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/download.imageset/download-2-6.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/download.imageset/download-2-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/download.imageset/download-2-7.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/email_icn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icons8-important-mail-50.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/email_icn.imageset/icons8-important-mail-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/email_icn.imageset/icons8-important-mail-50.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/floating_icn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "floating_icn.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "floating_icn@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "floating_icn@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/floating_icn.imageset/floating_icn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/floating_icn.imageset/floating_icn.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/floating_icn.imageset/floating_icn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/floating_icn.imageset/floating_icn@2x.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/floating_icn.imageset/floating_icn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/floating_icn.imageset/floating_icn@3x.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/flowcrypt-ios-icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "flowcrypt-ios-icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/flowcrypt-ios-icon.imageset/flowcrypt-ios-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/flowcrypt-ios-icon.imageset/flowcrypt-ios-icon.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/full-logo.imageset/1200-312-black-and-white-dark-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/full-logo.imageset/1200-312-black-and-white-dark-mode.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/full-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "1x", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "dark" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "universal", 19 | "filename" : "full-logo.png", 20 | "scale" : "2x" 21 | }, 22 | { 23 | "idiom" : "universal", 24 | "filename" : "1200-312-black-and-white-dark-mode.png", 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "universal", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "idiom" : "universal", 39 | "scale" : "3x", 40 | "appearances" : [ 41 | { 42 | "appearance" : "luminosity", 43 | "value" : "dark" 44 | } 45 | ] 46 | } 47 | ], 48 | "info" : { 49 | "version" : 1, 50 | "author" : "xcode" 51 | } 52 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/full-logo.imageset/full-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/full-logo.imageset/full-logo.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/gmail_icn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "gmail.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "gmail@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "gmail@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/gmail_icn.imageset/gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/gmail_icn.imageset/gmail.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/gmail_icn.imageset/gmail@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/gmail_icn.imageset/gmail@2x.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/gmail_icn.imageset/gmail@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/gmail_icn.imageset/gmail@3x.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/microsoft-outlook.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "microsoft-outlook@3x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/microsoft-outlook.imageset/microsoft-outlook@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/microsoft-outlook.imageset/microsoft-outlook@3x.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/plus.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "plus.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/plus.imageset/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/plus.imageset/plus.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/reply-all.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "reply-all.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "reply-all@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "reply-all@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/reply-all.imageset/reply-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/reply-all.imageset/reply-all.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/reply-all.imageset/reply-all@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/reply-all.imageset/reply-all@2x.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/reply-all.imageset/reply-all@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/reply-all.imageset/reply-all@3x.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/retry.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "1x", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "dark" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "universal", 19 | "filename" : "retry.png", 20 | "scale" : "2x" 21 | }, 22 | { 23 | "idiom" : "universal", 24 | "filename" : "retry_white.png", 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "universal", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "idiom" : "universal", 39 | "scale" : "3x", 40 | "appearances" : [ 41 | { 42 | "appearance" : "luminosity", 43 | "value" : "dark" 44 | } 45 | ] 46 | } 47 | ], 48 | "info" : { 49 | "version" : 1, 50 | "author" : "xcode" 51 | } 52 | } -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/retry.imageset/retry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/retry.imageset/retry.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/retry.imageset/retry_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/retry.imageset/retry_white.png -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/share.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "share-2.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlowCrypt/Assets.xcassets/share.imageset/share-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Assets.xcassets/share.imageset/share-2.png -------------------------------------------------------------------------------- /FlowCrypt/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FlowCrypt/Common UI/Refreshable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Refreshable.swift 3 | // FlowCrypt 4 | // 5 | // Created by Yevhen Kyivskyi on 04.09.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @MainActor 12 | protocol Refreshable { 13 | func startRefreshing() 14 | } 15 | -------------------------------------------------------------------------------- /FlowCrypt/Common UI/View Controllers/WebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 12/10/19. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | /** 13 | * InApp WebViewController to show web content. Used for showing privacy policy etc in splash screen and settings 14 | */ 15 | final class WebViewController: UIViewController { 16 | private lazy var webView = WKWebView() 17 | 18 | private var url: URL? 19 | 20 | init(url: URL?) { 21 | self.url = url 22 | super.init(nibName: nil, bundle: nil) 23 | } 24 | 25 | @available(*, unavailable) 26 | required init?(coder _: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | override func loadView() { 31 | view = webView 32 | webView.backgroundColor = .backgroundColor 33 | } 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | webView.allowsBackForwardNavigationGestures = false 38 | guard let lik = url else { return } 39 | webView.load(URLRequest(url: lik)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/Bootstrap/BootstrapViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BootstrapViewController.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 29/01/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | * View controller with activity indicator which is presented before all AppStartup activity finished (setup Core, migration of the DB...) 13 | */ 14 | final class BootstrapViewController: UIViewController { 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | view.backgroundColor = .backgroundColor 18 | let activityIndicator = UIActivityIndicatorView(style: .medium) 19 | activityIndicator.startAnimating() 20 | view.addSubview(activityIndicator) 21 | activityIndicator.center = view.center 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/Compose/ComposeMessageAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComposeMessageAction.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 19/09/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ComposeMessageAction { 12 | case update(MessageIdentifier), 13 | delete(MessageIdentifier), 14 | sent(MessageIdentifier) 15 | } 16 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/Compose/Extensions/ComposeViewController+Keyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComposeViewController+Keyboard.swift 3 | // FlowCrypt 4 | // 5 | // Created by Ioan Moldovan on 4/6/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import FlowCryptUI 10 | import UIKit 11 | 12 | // MARK: - Keyboard 13 | extension ComposeViewController { 14 | func observerAppStates() { 15 | NotificationCenter.default.addObserver( 16 | self, 17 | selector: #selector(startDraftTimer), 18 | name: UIApplication.didBecomeActiveNotification, 19 | object: nil 20 | ) 21 | 22 | NotificationCenter.default.addObserver( 23 | self, 24 | selector: #selector(stopDraftTimer), 25 | name: UIApplication.willResignActiveNotification, 26 | object: nil 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/Inbox/Container/InboxViewControllerContainerDecorator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InboxViewControllerContainerDecorator.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 27.11.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import FlowCryptUI 10 | import UIKit 11 | 12 | struct InboxViewControllerContainerDecorator { 13 | func emptyFoldersInput(with size: CGSize) -> TextCellNode.Input { 14 | TextCellNode.Input( 15 | backgroundColor: .backgroundColor, 16 | title: "error_no_folders".localized, 17 | withSpinner: false, 18 | size: size 19 | ) 20 | } 21 | 22 | func errorInput(with size: CGSize, error: Error) -> TextCellNode.Input { 23 | TextCellNode.Input( 24 | backgroundColor: .backgroundColor, 25 | title: "error_general_text".localized + "\n\n\(error.errorMessage)", 26 | withSpinner: false, 27 | size: size, 28 | insets: .deviceSpecificTextInsets(top: 8, bottom: 8) 29 | ) 30 | } 31 | 32 | func retryActionTitle() -> NSAttributedString { 33 | "retry_title".localized.attributed(color: .white) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/Settings/KeyLegal/LegalViewDecorator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LegalViewDecorator.swift 3 | // FlowCrypt 4 | // 5 | // Created by Yevhen Kyivskyi on 21.10.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct LegalViewDecorator { 12 | let sceneTitle = "settings_screen_legal".localized 13 | let insets = UIEdgeInsets.deviceSpecificTextInsets(top: 16, bottom: 16) 14 | 15 | func attributedSetting(_ title: String) -> NSAttributedString { 16 | title.attributed(.regular(16), color: .mainTextColor) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/Settings/Settings List/SettingsViewDecorator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewDecorator.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 12/9/19. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct SettingsViewDecorator { 12 | let sceneTitle = "settings_screen_title".localized 13 | let insets = UIEdgeInsets.deviceSpecificTextInsets(top: 16, bottom: 16) 14 | 15 | func attributedSetting(_ title: String) -> NSAttributedString { 16 | title.attributed(.regular(16), color: .mainTextColor) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/Setup/PassPhraseSaveable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassPhraseSaveable.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 02.06.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import FlowCryptUI 10 | 11 | @MainActor 12 | protocol PassPhraseSaveable { 13 | var storageMethod: PassPhraseStorageMethod { get set } 14 | var passPhraseIndexes: [IndexPath] { get } 15 | var saveLocallyNode: CellNode { get } 16 | var saveInMemoryNode: CellNode { get } 17 | func handleSelectedPassPhraseOption() 18 | func showPassPhraseErrorAlert() 19 | } 20 | 21 | extension PassPhraseSaveable where Self: TableNodeViewController { 22 | func handleSelectedPassPhraseOption() { 23 | node.reloadRows(at: passPhraseIndexes, with: .automatic) 24 | } 25 | 26 | var saveLocallyNode: CellNode { 27 | CheckBoxTextNode(input: .passPhraseLocally(isSelected: storageMethod == .persistent)) 28 | } 29 | 30 | var saveInMemoryNode: CellNode { 31 | CheckBoxTextNode(input: .passPhraseMemory(isSelected: storageMethod == .memory)) 32 | } 33 | 34 | func showPassPhraseErrorAlert() { 35 | showAlert(message: "setup_enter_pass_phrase".localized) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/SideMenu/NavigationController/NavigationChildController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationChildController.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 12.07.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @MainActor 12 | protocol NavigationChildController { 13 | var navigationItem: UINavigationItem { get } 14 | var shouldShowBackButton: Bool { get } 15 | func handleBackButtonTap() 16 | } 17 | 18 | extension NavigationChildController where Self: UIViewController { 19 | var shouldShowBackButton: Bool { true } 20 | func handleBackButtonTap() {} 21 | } 22 | -------------------------------------------------------------------------------- /FlowCrypt/Controllers/Threads/Models/MessageThread.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageThread.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 12.10.2021 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct MessageThreadContext { 12 | let threads: [MessageThread] 13 | let pagination: MessagesListPagination 14 | } 15 | 16 | struct MessageThread: Equatable { 17 | let identifier: String? 18 | let snippet: String? 19 | var messages: [Message] 20 | } 21 | -------------------------------------------------------------------------------- /FlowCrypt/Core/Models/DecryptedPrivateKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecryptedPrivateKey.swift 3 | // FlowCrypt 4 | // 5 | // Created by Yevhen Kyivskyi on 16.07.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct DecryptedPrivateKeysResponse: Decodable { 12 | let privateKeys: [DecryptedPrivateKey] 13 | 14 | struct DecryptedPrivateKey: Decodable { 15 | let decryptedPrivateKey: String 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FlowCrypt/Core/Models/KeyAlgo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyAlgo.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 23/08/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct KeyAlgo: Decodable { 12 | let algorithm: String 13 | let algorithmId: Int 14 | let bits: Int? 15 | let curve: String? 16 | } 17 | -------------------------------------------------------------------------------- /FlowCrypt/Core/Models/KeyId.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyId.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 17/07/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct KeyId: Decodable { 12 | let longid: String 13 | let fingerprint: String 14 | 15 | init(longid: String, fingerprint: String) { 16 | self.longid = longid 17 | self.fingerprint = fingerprint 18 | } 19 | } 20 | 21 | extension KeyId: Hashable { 22 | func hash(into hasher: inout Hasher) { 23 | hasher.combine(fingerprint) 24 | } 25 | } 26 | 27 | extension KeyId: Equatable { 28 | static func == (lhs: KeyId, rhs: KeyId) -> Bool { 29 | lhs.fingerprint == rhs.fingerprint 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FlowCrypt/FlowCrypt-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "ObjcException.h" 6 | -------------------------------------------------------------------------------- /FlowCrypt/FlowCrypt.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.default-data-protection 6 | NSFileProtectionComplete 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlowCrypt/FlowCryptEnterprise.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.default-data-protection 6 | NSFileProtectionComplete 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlowCrypt/FlowCryptRelease.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.default-data-protection 6 | NSFileProtectionComplete 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Api/Account Server Apis/BackendApi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // © 2017-2019 FlowCrypt Limited. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | /// Backend API for regular consumers and small businesses 8 | /// (not implemented on iOS yet) 9 | final class BackendApi { 10 | static let shared = BackendApi() 11 | 12 | private init() {} 13 | 14 | private static func url(endpoint: String) -> String { 15 | "https://flowcrypt.com/api/\(endpoint)" 16 | } 17 | 18 | private static func normalize(_ email: String) -> String { 19 | email.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Api/Account Server Apis/Models/EnterpriseServerApiError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnterpriseServerApiError.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 27/12/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum EnterpriseServerApiError: Error { 12 | case parse 13 | case emailFormat 14 | case noActiveFesUrl 15 | } 16 | 17 | extension EnterpriseServerApiError: LocalizedError { 18 | var errorDescription: String? { 19 | switch self { 20 | case .parse: return "organisational_rules_parse_error_description".localized 21 | case .emailFormat: return "organisational_rules_email_format_error_description".localized 22 | case .noActiveFesUrl: return "organisational_rules_fes_url_error_description".localized 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Api/Account Server Apis/Models/MessageUploadDetails.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageUploadDetails.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 30/12/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct MessageUploadDetails: Encodable { 12 | let associateReplyToken: String 13 | let from: String 14 | let to: [String] 15 | let cc: [String] 16 | let bcc: [String] 17 | } 18 | 19 | extension MessageUploadDetails { 20 | init(from message: SendableMsg, replyToken: String) { 21 | self.associateReplyToken = replyToken 22 | self.from = message.from 23 | self.to = message.to 24 | self.cc = message.cc 25 | self.bcc = message.bcc 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Api/IdTokenUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IdTokenUtils.swift 3 | // FlowCrypt 4 | // 5 | // Created by Ioan Moldovan on 3/25/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum IdTokenUtils { 12 | // Get id token from user or user email 13 | static func getIdToken(user: User? = nil, userEmail: String? = nil) async throws -> String { 14 | if let user, case .password = user.authType { 15 | return try Imap(user: user).imapSess.oAuth2Token 16 | } 17 | 18 | let googleAuthManager = GoogleAuthManager() 19 | 20 | return try await googleAuthManager.getCachedOrRefreshedIdToken(email: userEmail ?? user?.email) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/DataManager/Local Storage/LocalStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalStorage.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 25.11.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol LocalStorageType { 12 | var storage: UserDefaults { get } 13 | 14 | var trashFolderPath: String? { get } 15 | func saveTrashFolder(path: String) 16 | 17 | func cleanup() 18 | } 19 | 20 | struct LocalStorage: LocalStorageType { 21 | private enum Constants: String, CaseIterable { 22 | case indexTrashFolder 23 | } 24 | 25 | let storage: UserDefaults 26 | 27 | init(storage: UserDefaults = .standard) { 28 | self.storage = storage 29 | } 30 | } 31 | 32 | extension LocalStorage { 33 | var trashFolderPath: String? { 34 | storage.string(forKey: Constants.indexTrashFolder.rawValue) 35 | } 36 | 37 | func saveTrashFolder(path: String) { 38 | storage.set(path, forKey: Constants.indexTrashFolder.rawValue) 39 | } 40 | } 41 | 42 | extension LocalStorage { 43 | func cleanup() { 44 | for key in Constants.allCases.map(\.rawValue) { 45 | storage.removeObject(forKey: key) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Backup Provider/BackupApiClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackupApiClient.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 26.12.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol BackupApiClient { 12 | func searchBackups(for email: String) async throws -> Data 13 | } 14 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Contacts Provider/ContactsProviderType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsProviderType.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 07.12.2022 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import GTMAppAuth 11 | 12 | protocol ContactsProviderType { 13 | var authorization: GTMAppAuth.AuthSession? { get set } 14 | var isContactsScopeEnabled: Bool { get } 15 | func searchContacts(query: String) async throws -> [Recipient] 16 | } 17 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Imap/Imap+msg.swift: -------------------------------------------------------------------------------- 1 | // 2 | // © 2017-2019 FlowCrypt Limited. All rights reserved. 3 | // 4 | 5 | import MailCore 6 | 7 | extension Imap { 8 | 9 | func fetchMsg(message: MCOIMAPMessage, folder: String) async throws -> Message { 10 | throw (AppErr.unexpected("Should be implemented")) 11 | // return try await execute("fetchMsg", { sess, respond in 12 | // sess.fetchMessageOperation( 13 | // withFolder: folder, 14 | // uid: message.uid 15 | // ).start { error, value in respond(error, value) } 16 | // }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Imap/ImapError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImapError.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 07.12.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ImapError: Error { 12 | case noSession 13 | case providerError(Error) 14 | case missingMessageInfo(String) 15 | case folderRequired 16 | case createSearchExpression 17 | } 18 | 19 | extension ImapError: CustomStringConvertible { 20 | var description: String { 21 | switch self { 22 | case .noSession: 23 | return "imap_error_no_session".localized 24 | case let .providerError(error): 25 | return "imap_error_provider".localizeWithArguments(error.localizedDescription) 26 | case let .missingMessageInfo(message): 27 | return "imap_error_msg_info".localizeWithArguments(message) 28 | case .folderRequired: 29 | return "imap_error_folder_required".localized 30 | case .createSearchExpression: 31 | return "imap_error_create_search_expression".localized 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Imap/ImapSessionProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImapSessionProvider.swift 3 | // FlowCrypt 4 | // 5 | // Created by Tom on 30.11.2021 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ImapSessionProviderType { 12 | func imapSession() throws -> IMAPSession 13 | func smtpSession() throws -> SMTPSession 14 | } 15 | 16 | class ImapSessionProvider: ImapSessionProviderType { 17 | 18 | private let user: User 19 | 20 | init(user: User) { 21 | self.user = user 22 | } 23 | 24 | func imapSession() throws -> IMAPSession { 25 | return try IMAPSession(user: self.user) 26 | } 27 | 28 | func smtpSession() throws -> SMTPSession { 29 | return try SMTPSession(user: self.user) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Imap/MessageKindProviderType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageKindProviderType.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 8/29/19. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import MailCore 10 | 11 | protocol MessageKindProviderType { 12 | var messagesRequestKind: Int { get } 13 | var imapMessagesRequestKind: MCOIMAPMessagesRequestKind { get } 14 | } 15 | 16 | struct MessageKindProvider: MessageKindProviderType { 17 | var messagesRequestKind: Int { 18 | MCOIMAPMessagesRequestKind.headers.rawValue 19 | | MCOIMAPMessagesRequestKind.structure.rawValue 20 | | MCOIMAPMessagesRequestKind.internalDate.rawValue 21 | | MCOIMAPMessagesRequestKind.headerSubject.rawValue 22 | | MCOIMAPMessagesRequestKind.flags.rawValue 23 | | MCOIMAPMessagesRequestKind.size.rawValue 24 | } 25 | 26 | var imapMessagesRequestKind: MCOIMAPMessagesRequestKind { 27 | MCOIMAPMessagesRequestKind(rawValue: messagesRequestKind) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/ConnectionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionType.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 03/04/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import MailCore 10 | 11 | enum AuthType { 12 | /// gmail authentication with token 13 | case oAuthGmail(String) 14 | /// other email provider with password 15 | case password(String) 16 | } 17 | 18 | enum ConnectionType: String, CaseIterable { 19 | case none, tls, startls 20 | } 21 | 22 | extension MCOConnectionType { 23 | init?(_ connectionType: ConnectionType) { 24 | switch connectionType { 25 | case .tls: self = .TLS 26 | case .startls: self = .startTLS 27 | case .none: return nil 28 | } 29 | } 30 | } 31 | 32 | extension ConnectionType { 33 | init(_ connectionType: MCOConnectionType) { 34 | switch connectionType { 35 | case .TLS: self = .tls 36 | case .startTLS: self = .startls 37 | default: self = .tls 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/MailSettingsCredentials.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MailSettingsCredentials.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 01/04/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import MailCore 10 | 11 | struct MailSettingsCredentials { 12 | let hostName: String? 13 | let port: Int 14 | let connectionType: ConnectionType 15 | } 16 | 17 | extension MailSettingsCredentials { 18 | init(_ service: MCONetService) { 19 | self.hostName = service.hostname 20 | self.port = Int(service.port) 21 | self.connectionType = ConnectionType(service.connectionType) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Message Gateway/Imap+send.swift: -------------------------------------------------------------------------------- 1 | // 2 | // © 2017-2019 FlowCrypt Limited. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | extension Imap: MessageGateway { 8 | func sendMail(input: MessageGatewayInput, progressHandler: ((Float) -> Void)?) async throws -> Identifier { 9 | try await withCheckedThrowingContinuation { [weak self] continuation in 10 | do { 11 | let session = try self?.smtpSess 12 | session?.sendOperation(with: input.mime) 13 | .start { error in 14 | if let error { 15 | return continuation.resume(throwing: error) 16 | } 17 | return continuation.resume(throwing: AppErr.unexpected("Not implemented")) 18 | } 19 | } catch { 20 | return continuation.resume(throwing: ImapError.noSession) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Message Gateway/MessageGateway.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageGateway.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 04.11.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct MessageGatewayInput { 12 | let mime: Data 13 | let threadId: String? 14 | } 15 | 16 | protocol MessageGateway { 17 | func sendMail(input: MessageGatewayInput, progressHandler: ((Float) -> Void)?) async throws -> Identifier 18 | } 19 | 20 | protocol DraftsApiClient { 21 | func fetchDraft(id: Identifier) async throws -> MessageIdentifier? 22 | func fetchDraftIdentifier(for messageId: Identifier) async throws -> MessageIdentifier? 23 | func saveDraft(input: MessageGatewayInput, draftId: Identifier?) async throws -> MessageIdentifier 24 | func deleteDraft(with identifier: Identifier) async throws 25 | } 26 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/Message Provider/MessageProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageProvider.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 29.11.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol MessageProvider { 12 | func fetchMessage(id: Identifier, folder: String) async throws -> Message 13 | func fetchRawMessage(id: Identifier) async throws -> String 14 | func fetchAttachment( 15 | id: Identifier, 16 | messageId: Identifier, 17 | estimatedSize: Float?, 18 | progressHandler: ((Float) -> Void)? 19 | ) async throws -> Data 20 | } 21 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/MessageOperations Provider/MessageOperationsApiClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageOperationsApiClient.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 07.12.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import FlowCryptCommon 10 | 11 | protocol MessageOperationsApiClient { 12 | func moveMessageToTrash(id: Identifier, trashPath: String?, from folder: String) async throws 13 | func deleteMessage(id: Identifier, from folderPath: String?) async throws 14 | func moveMessageToInbox(id: Identifier, folderPath: String) async throws 15 | func archiveMessage(id: Identifier, folderPath: String) async throws 16 | func markAsUnread(id: Identifier, folder: String) async throws 17 | func markAsRead(id: Identifier, folder: String) async throws 18 | func emptyFolder(path: String) async throws 19 | func batchDeleteMessages(identifiers: [String], from folderPath: String?) async throws 20 | } 21 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageContext.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 19/12/2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct MessageContext { 12 | let messages: [Message] 13 | let pagination: MessagesListPagination 14 | } 15 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageIdentifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageIdentifier.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 20/09/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import GoogleAPIClientForREST_Gmail 10 | 11 | struct MessageIdentifier { 12 | var draftId: Identifier? 13 | var threadId: Identifier? 14 | var messageId: Identifier? 15 | var draftMessageId: Identifier? 16 | } 17 | 18 | extension MessageIdentifier { 19 | init(gmailDraft: GTLRGmail_Draft) { 20 | self.draftId = Identifier(stringId: gmailDraft.identifier) 21 | self.threadId = Identifier(stringId: gmailDraft.message?.threadId) 22 | self.messageId = Identifier(stringId: gmailDraft.message?.identifier) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/MessageQuoteType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageQuoteType.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 23/11/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum MessageQuoteType { 12 | case reply, replyAll, forward 13 | } 14 | 15 | extension MessageQuoteType { 16 | var subjectPrefix: String { 17 | switch self { 18 | case .reply, .replyAll: 19 | return "re".localized 20 | case .forward: 21 | return "fwd".localized 22 | } 23 | } 24 | 25 | var actionLabel: String { 26 | switch self { 27 | case .reply: 28 | return "" 29 | case .replyAll: 30 | return "message_reply_all".localized 31 | case .forward: 32 | return "forward".localized 33 | } 34 | } 35 | 36 | var accessibilityIdentifier: String { 37 | switch self { 38 | case .reply: 39 | return "aid-reply-button" 40 | case .replyAll: 41 | return "aid-reply-all-button" 42 | case .forward: 43 | return "aid-forward-button" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/Gmail+Search.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Gmail+Search.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 25.12.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | extension GmailService: MessageSearchApiClient { 10 | func searchExpression(using context: MessageSearchContext) async throws -> [Message] { 11 | try await fetchMessages( 12 | using: FetchMessageContext(searchContext: context) 13 | ).messages 14 | } 15 | } 16 | 17 | extension FetchMessageContext { 18 | init(searchContext: MessageSearchContext) { 19 | let folder = searchContext.folderPath ?? "anywhere" 20 | let query = "in:\(folder) \(searchContext.expression) OR subject:\(searchContext.expression)" 21 | self.init( 22 | folderPath: searchContext.folderPath, 23 | count: searchContext.count, 24 | searchQuery: query, 25 | pagination: nil 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/MessageSearchApiClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageSearchApiClient.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 25.12.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | enum MessageSearchDestinations: CaseIterable { 10 | case subject, from, to, recipient, content, body 11 | } 12 | 13 | struct MessageSearchContext { 14 | let expression: String 15 | let folderPath: String? 16 | let searchDestinations: [MessageSearchDestinations] 17 | let count: Int? 18 | let from: Int 19 | 20 | init( 21 | expression: String, 22 | folderPath: String? = nil, 23 | searchDestinations: [MessageSearchDestinations] = MessageSearchDestinations.allCases, 24 | count: Int? = nil, 25 | from: Int = 0 26 | ) { 27 | self.expression = expression 28 | self.folderPath = folderPath 29 | self.searchDestinations = searchDestinations 30 | self.count = count 31 | self.from = from 32 | } 33 | } 34 | 35 | protocol MessageSearchApiClient { 36 | func searchExpression(using context: MessageSearchContext) async throws -> [Message] 37 | } 38 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Mail Provider/UsersMailSession Provider/UserMailSessionProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserMailSessionProvider.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 10.02.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | protocol UsersMailSessionProvider { 10 | func renewSession() async throws 11 | } 12 | 13 | // MARK: - GmailService 14 | extension GmailService: UsersMailSessionProvider { 15 | func renewSession() async throws { 16 | // GTMAppAuth should renew session via OIDAuthStateChangeDelegate 17 | } 18 | } 19 | 20 | // MARK: - Imap 21 | extension Imap: UsersMailSessionProvider { 22 | func renewSession() async throws { 23 | try setupSession() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Services/Backups Manager/BackupsManagerError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackupsManagerError.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 27.11.2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum BackupsManagerError: Error { 12 | case parse 13 | case keyIsNotFullyEncrypted 14 | } 15 | 16 | extension BackupsManagerError: CustomStringConvertible { 17 | var description: String { 18 | switch self { 19 | case .parse: 20 | return "backupManagerError_parse".localized 21 | case .keyIsNotFullyEncrypted: 22 | return "backupManagerError_notEncrypted".localized 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Services/Backups Manager/BackupsManagerType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackupsManagerType.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 31.05.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol BackupsManagerType { 12 | /// get all existed backups 13 | func fetchBackupsFromInbox(for userId: UserId) async throws -> [KeyDetails] 14 | /// backup keys to user inbox 15 | func backupToInbox(keys: [KeyDetails], for userId: UserId) async throws 16 | /// show activity sheet to save keys as file 17 | func backupAsFile(keys: [KeyDetails], for viewController: UIViewController) 18 | } 19 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Services/Compose Message Helper/ComposeMessageRecipient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComposeMessageRecipient.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 17/12/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ComposeMessageRecipient: RecipientBase { 12 | let email: String 13 | var name: String? 14 | let type: RecipientType 15 | var state: RecipientState 16 | var keyState: PubKeyState? 17 | } 18 | 19 | extension ComposeMessageRecipient: Equatable { 20 | static func == (lhs: ComposeMessageRecipient, rhs: ComposeMessageRecipient) -> Bool { 21 | return lhs.email == rhs.email && lhs.type == rhs.type 22 | } 23 | } 24 | 25 | enum RecipientType: String, CaseIterable, Hashable { 26 | case from, to, cc, bcc 27 | } 28 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Services/Folders Manager/RemoteFoldersApiClient/RemoteFoldersApiClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteFoldersApiClient.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 06/09/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol RemoteFoldersApiClient { 12 | func fetchFolders() async throws -> [Folder] 13 | } 14 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Services/Folders Manager/TrashFolderProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrashFolderProvider.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 20.02.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | struct TrashFolderProvider { 10 | private let localStorage: LocalStorageType 11 | private let foldersManager: FoldersManagerType 12 | private let user: User 13 | 14 | init( 15 | user: User, 16 | foldersManager: FoldersManagerType, 17 | localStorage: LocalStorageType = LocalStorage() 18 | ) { 19 | self.foldersManager = foldersManager 20 | self.localStorage = localStorage 21 | self.user = user 22 | } 23 | } 24 | 25 | extension TrashFolderProvider: TrashFolderProviderType { 26 | var trashFolderPath: String? { 27 | get async throws { 28 | if let path = localStorage.trashFolderPath { 29 | return path 30 | } else { 31 | _ = try await foldersManager.fetchFolders(isForceReload: true, for: user) 32 | return localStorage.trashFolderPath 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Services/Google User Service/IdToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IdToken.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 07.12.2022 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct IdToken: Codable { 12 | let exp: Int 13 | } 14 | 15 | extension IdToken { 16 | var expiryDuration: Double { 17 | Date(timeIntervalSince1970: Double(exp)).timeIntervalSinceNow 18 | } 19 | } 20 | 21 | enum IdTokenError: Error, CustomStringConvertible { 22 | case missingToken, invalidJWTFormat, invalidBase64EncodedData 23 | 24 | var description: String { 25 | switch self { 26 | case .missingToken: 27 | return "id_token_missing_error_description".localized 28 | case .invalidJWTFormat, .invalidBase64EncodedData: 29 | return "id_token_invalid_error_description".localized 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowCrypt/Functionality/Services/SendAs Provider/RemoteSendAsApiClient/RemoteSendAsApiClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteSendAsApiClient.swift 3 | // FlowCrypt 4 | // 5 | // Created by Ioan Moldovan on 06/13/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol RemoteSendAsApiClient { 12 | func fetchSendAsList() async throws -> [SendAsModel] 13 | } 14 | -------------------------------------------------------------------------------- /FlowCrypt/Lib/ObjcException/ObjcException.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjcException.h 3 | // FlowCrypt 4 | // 5 | // Created by  Ivan Ushakov on 25.01.2022 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ObjcException : NSObject 14 | 15 | + (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /FlowCrypt/Lib/ObjcException/ObjcException.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjcException.m 3 | // FlowCrypt 4 | // 5 | // Created by  Ivan Ushakov on 25.01.2022 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | #import "ObjcException.h" 10 | 11 | @implementation ObjcException 12 | 13 | + (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { 14 | @try 15 | { 16 | tryBlock(); 17 | return YES; 18 | } 19 | @catch (NSException *exception) 20 | { 21 | NSMutableDictionary *userInfo = [NSMutableDictionary new]; 22 | if (exception.userInfo != NULL) 23 | { 24 | userInfo = [[NSMutableDictionary alloc] initWithDictionary:exception.userInfo]; 25 | } 26 | 27 | if (exception.reason != nil) 28 | { 29 | if (![userInfo.allKeys containsObject:NSLocalizedFailureReasonErrorKey]) 30 | { 31 | [userInfo setObject:exception.reason forKey:NSLocalizedFailureReasonErrorKey]; 32 | } 33 | } 34 | 35 | *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:userInfo]; 36 | return NO; 37 | } 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /FlowCrypt/Models/Common/Folder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Folder.swift 3 | // FlowCrypt 4 | // 5 | // Created by  Ivan Ushakov on 16.11.2021 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Folder { 12 | var path: String 13 | var name: String 14 | var image: Data? 15 | var backgroundColor: String? 16 | var isHidden: Bool? 17 | var itemType: String = FolderViewModel.ItemType.folder.rawValue 18 | } 19 | -------------------------------------------------------------------------------- /FlowCrypt/Models/Common/RecipientBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecipientBase.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 17/02/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol RecipientBase { 12 | var email: String { get } 13 | var name: String? { get } 14 | } 15 | 16 | extension RecipientBase { 17 | var formatted: String { 18 | guard let name else { return email } 19 | return "\(name) <\(email)>" 20 | } 21 | 22 | var shortName: String { 23 | name?.components(separatedBy: " ").first ?? 24 | email.components(separatedBy: "@").first ?? 25 | "unknown" 26 | } 27 | 28 | var displayName: String { 29 | if let name, !name.isEmpty { 30 | return name 31 | } 32 | return email 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FlowCrypt/Models/Contact Models/PubKeyState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PubKeyState.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 21/10/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum PubKeyState { 12 | case active, expired, revoked, empty, notUsableForEncryption, notUsableForSigning 13 | } 14 | -------------------------------------------------------------------------------- /FlowCrypt/Models/Document.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Document.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 19.11.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class Document: UIDocument { 12 | var data: Data? 13 | 14 | override func contents(forType _: String) throws -> Any { 15 | guard let data else { return Data() } 16 | return try NSKeyedArchiver.archivedData( 17 | withRootObject: data, 18 | requiringSecureCoding: true 19 | ) 20 | } 21 | 22 | override func load(fromContents contents: Any, ofType _: String?) throws { 23 | guard let data = contents as? Data else { return } 24 | self.data = data 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FlowCrypt/Models/Inbox Models/InboxViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InboxViewModel.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 8/21/19. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct InboxViewModel { 12 | let folderName: String 13 | var path: String 14 | 15 | init(folderName: String, path: String) { 16 | self.folderName = folderName 17 | self.path = folderName.isEmpty ? "Inbox" : path 18 | } 19 | } 20 | 21 | extension InboxViewModel { 22 | init(_ folder: FolderViewModel) { 23 | self.init(folderName: folder.name, path: folder.path) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FlowCrypt/Models/Realm Models/FolderRealmObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderRealmObject.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 31/08/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import RealmSwift 10 | 11 | final class FolderRealmObject: Object { 12 | @Persisted(primaryKey: true) var path: String // swiftlint:disable:this attributes 13 | @Persisted var name: String 14 | @Persisted var image: Data? 15 | @Persisted var itemType: String 16 | @Persisted var user: UserRealmObject? 17 | @Persisted var backgroundColor: String? 18 | @Persisted var isHidden: Bool? 19 | } 20 | 21 | extension FolderRealmObject { 22 | convenience init(folder: Folder, user: User) { 23 | self.init() 24 | self.path = folder.path 25 | self.name = folder.name 26 | self.image = folder.image 27 | self.itemType = folder.itemType 28 | self.user = UserRealmObject(user) 29 | self.backgroundColor = folder.backgroundColor 30 | self.isHidden = folder.isHidden 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowCrypt/Models/Realm Models/SendAsRealmObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SendAsRealmObject.swift 3 | // FlowCrypt 4 | // 5 | // Created by Ioan Moldovan on 6/13/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import RealmSwift 10 | 11 | final class SendAsRealmObject: Object { 12 | @Persisted(primaryKey: true) var sendAsEmail: String // swiftlint:disable:this attributes 13 | @Persisted var displayName: String 14 | @Persisted var verificationStatus: String 15 | @Persisted var signature: String 16 | @Persisted var isDefault: Bool 17 | @Persisted var user: UserRealmObject? 18 | } 19 | 20 | extension SendAsRealmObject { 21 | convenience init(sendAs: SendAsModel, user: User) { 22 | self.init() 23 | self.displayName = sendAs.displayName 24 | self.sendAsEmail = sendAs.sendAsEmail 25 | self.verificationStatus = sendAs.verificationStatus.rawValue 26 | self.isDefault = sendAs.isDefault 27 | self.signature = sendAs.signature 28 | self.user = UserRealmObject(user) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FlowCrypt/Models/Realm Models/SessionRealmObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionRealmObject.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 07/04/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import RealmSwift 10 | 11 | final class SessionRealmObject: Object { 12 | @Persisted var hostname: String 13 | @Persisted var port: Int 14 | @Persisted var username: String 15 | @Persisted var password: String? 16 | @Persisted var oAuth2Token: String? 17 | @Persisted var connectionType: String 18 | @Persisted var email: String? 19 | } 20 | 21 | extension SessionRealmObject { 22 | convenience init(_ session: Session) { 23 | self.init() 24 | self.hostname = session.hostname 25 | self.port = session.port 26 | self.username = session.username 27 | self.password = session.password 28 | self.oAuth2Token = session.oAuth2Token 29 | self.connectionType = session.connectionType 30 | self.email = session.email 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowCrypt/Resources/UI Guidance.md: -------------------------------------------------------------------------------- 1 | ## FlowCrypt UI Guidance 2 | 3 | Instead of using UIKit views and layout them we can use ASTableNode (similar to UITableView). 4 | and build or UI in more flexible and reusable way by layouting independent ASTableNodeCells (UITableViewCell) 5 | They will not dependent on already existed elements. So for example we can add or remove parts without touching any other elements 6 | ASDK (Texture) can calculate UI not on Main Thread. For this we can use ASCellNodeBlock with ASCellNodes as return element 7 | 8 | For existed nodes please check FlowCryptUI. This contains all elements which are used it the app. 9 | Please use elements from FlowCryptUI or add new one. 10 | Consider this as design system for the app. 11 | 12 | Each node built in declarative way and can be build with Input (consider as view model for node) 13 | 14 | For button - ButtonCellNode can be used with appropriate ButtonCellNode.Input 15 | For titles - SetupTitleNode with input 16 | 17 | Usually all inputs for node can be build with attributed text, insets and action if supported 18 | There are a lot of usefull extensions for attributed text which uses common styling in app 19 | (FlowCryptCommon -> String extensions) 20 | 21 | Also app use Decorators for each View Controller focused on ui styling. 22 | -------------------------------------------------------------------------------- /FlowCrypt/Resources/flowcrypt-ios-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/FlowCrypt/Resources/flowcrypt-ios-icon.png -------------------------------------------------------------------------------- /FlowCrypt/Resources/ru.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /FlowCrypt/Resources/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "loading_title" = "Загрузка"; 2 | 3 | -------------------------------------------------------------------------------- /FlowCrypt/Resources/simple_webview_file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple HTML file to load the flowcrypt-ios-prod.js.txt file in a web view. 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Core/data.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. 2 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Extensions/StringTestExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringTestExtension.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by Roma Sosnovsky on 28/10/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | static func random(length: Int) -> String { 13 | let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 14 | return String((0 ..< length).map { _ in letters.randomElement()! }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Extensions/XCTestCaseExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCaseExtension.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by Roma Sosnovsky on 29/03/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | extension XCTestCase { 12 | func assert( 13 | _ expression: @autoclosure () throws -> some Any, 14 | throws error: E, 15 | in file: StaticString = #file, 16 | line: UInt = #line 17 | ) { 18 | var thrownError: Error? 19 | 20 | XCTAssertThrowsError(try expression(), 21 | file: file, line: line) { 22 | thrownError = $0 23 | } 24 | 25 | XCTAssertTrue( 26 | thrownError is E, 27 | "Unexpected error type: \(type(of: thrownError))", 28 | file: file, line: line 29 | ) 30 | 31 | XCTAssertEqual( 32 | thrownError as? E, error, 33 | file: file, line: line 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Functionality/Apis/Account Server Apis/EnterpriseServerApiTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnterpriseServerApiTests.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by  Ivan Ushakov on 04.12.2021 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | @testable import FlowCrypt 10 | import XCTest 11 | 12 | final class EnterpriseServerApiTests: XCTestCase { 13 | 14 | func testGetClientConfigurationWithUnknownDomain() async throws { 15 | let service = try EnterpriseServerApi(email: "user@nonexistentdomain.test") 16 | let configuration = try await service.getClientConfiguration() 17 | XCTAssertEqual(configuration, .empty) 18 | } 19 | 20 | func testGetClientConfigurationWithoutActiveFesUrl() async throws { 21 | let service = try EnterpriseServerApi(email: "user@gmail.com") 22 | let configuration = try await service.getClientConfiguration() 23 | XCTAssertEqual(configuration, .empty) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Functionality/FilesManager/FileMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileMock.swift 3 | // FlowCryptTests 4 | // 5 | // Created by Yevhen Kyivskyi on 17.06.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FileMock: FileType { 12 | let name: String 13 | let data: Data 14 | } 15 | 16 | extension FileMock { 17 | static let stringedFile = FileMock( 18 | name: "mock_file.pdf", 19 | data: "mocktext".data(using: .utf8)! 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/CurrentUserEmailMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentUserEmailMock.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by Anton Kharchevskyi on 21.09.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class CurrentUserEmailMock { 12 | var currentUserEmailCall: () -> (String?) = { 13 | nil 14 | } 15 | 16 | func currentUserEmail() -> String? { 17 | currentUserEmailCall() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Functionality/Services/Key Services/PubLookupTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PubLookupTest.swift 3 | // FlowCrypt 4 | // 5 | // Created by Tom on 27/10/2021 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | @testable import FlowCrypt 10 | import XCTest 11 | 12 | class PubLookupTest: XCTestCase { 13 | 14 | func lookupFromAttesterWithDifferentPgpUidAllowed() async throws { 15 | // tests https://github.com/FlowCrypt/flowcrypt-ios/issues/809 16 | // fetches https://flowcrypt.com/attester/pub/different.uid@recipient.test 17 | // if this test starts failing, ensure the right pubkey is still on prod Attester 18 | let pubLookup = PubLookup( 19 | clientConfiguration: ClientConfiguration( 20 | raw: RawClientConfiguration() 21 | ), 22 | localContactsProvider: LocalContactsProviderMock() 23 | ) 24 | let r = try await pubLookup.lookup(recipient: Recipient(email: "different.uid@recipient.test")) 25 | XCTAssertTrue(r.pubKeys.isNotEmpty, "expected pubkeys not empty") 26 | XCTAssertEqual(r.pubKeys.first?.longid, "0C9C2E6A4D273C6F") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Functionality/WKDURLs/ZBase32EncodingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZBase32EncodingTests.swift 3 | // FlowCryptTests 4 | // 5 | // Created by Yevhen Kyivskyi on 17.05.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import CryptoKit 10 | import XCTest 11 | 12 | class ZBase32EncodingTests: XCTestCase { 13 | 14 | func test_string_encoding() throws { 15 | let inputString = "example@email.com" 16 | let encodedString = "cihgn5mopt1wy3mpcfwsamudp7so" 17 | 18 | XCTAssert( 19 | String(decoding: inputString.data().zBase32EncodedBytes(), as: Unicode.UTF8.self) == encodedString 20 | ) 21 | } 22 | 23 | func test_hashed_string_encoding() throws { 24 | let inputString = "example@email.com" 25 | let encodedString = "8dkp15twcw7feu1i8em784qtw91y3cs7" 26 | 27 | let hashedInputString = Data(Insecure.SHA1.hash(data: inputString.data())) 28 | XCTAssert( 29 | String(decoding: hashedInputString.zBase32EncodedBytes(), as: Unicode.UTF8.self) == encodedString 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlowCryptAppTests/LocalStorageTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalStorageTests.swift 3 | // FlowCryptTests 4 | // 5 | // Created by Anton Kharchevskyi on 27.02.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | @testable import FlowCrypt 10 | import XCTest 11 | 12 | class LocalStorageTests: XCTestCase { 13 | var sut: LocalStorage! 14 | let testPassPhraseAccount = "passphrase@account.test" 15 | 16 | override func setUpWithError() throws { 17 | try super.setUpWithError() 18 | sut = LocalStorage() 19 | } 20 | 21 | var trashKey: String { 22 | "indexTrashFolder" 23 | } 24 | 25 | func testSaveTrashFolder() { 26 | let somePath = "dummyPath/gmail/trash" 27 | sut.saveTrashFolder(path: somePath) 28 | XCTAssertTrue(UserDefaults.standard.string(forKey: trashKey) == somePath) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Mocks/CoreComposeMessageMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreComposeMessageMock.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by Anton Kharchevskyi on 25.07.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | @testable import FlowCrypt 10 | 11 | class CoreComposeMessageMock: CoreComposeMessageType, KeyParser { 12 | var composeEmailResult: ((SendableMsg, MsgFmt) -> (CoreRes.ComposeEmail))! 13 | func composeEmail(msg: SendableMsg, fmt: MsgFmt) async throws -> CoreRes.ComposeEmail { 14 | return composeEmailResult(msg, fmt) 15 | } 16 | 17 | var parseKeysResult: ((Data) -> (CoreRes.ParseKeys))! 18 | func parseKeys(armoredOrBinary: Data) throws -> CoreRes.ParseKeys { 19 | return parseKeysResult(armoredOrBinary) 20 | } 21 | 22 | var encryptMsgResult: ((Data, [String]?, String?) -> Data)! 23 | func encrypt(data: Data, pubKeys: [String]?, password: String?) async throws -> Data { 24 | return encryptMsgResult(data, pubKeys, password) 25 | } 26 | 27 | var encryptFileResult: ((Data, String, [String]?) -> Data)! 28 | func encrypt(file: Data, name: String, pubKeys: [String]?) async throws -> Data { 29 | return encryptFileResult(file, name, pubKeys) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Mocks/DraftGatewayMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DraftGatewayMock.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by Evgenii Kyivskyi on 10/28/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | @testable import FlowCrypt 10 | 11 | class DraftsApiClientMock: DraftsApiClient { 12 | func fetchDraft(id: Identifier) async throws -> MessageIdentifier? { 13 | return nil 14 | } 15 | 16 | func fetchDraftIdentifier(for messageId: Identifier) async throws -> MessageIdentifier? { 17 | return nil 18 | } 19 | 20 | func saveDraft(input: MessageGatewayInput, draftId: Identifier?) async throws -> MessageIdentifier { 21 | return MessageIdentifier(draftId: draftId ?? .random, threadId: nil, messageId: nil) 22 | } 23 | 24 | func deleteDraft(with identifier: Identifier) async {} 25 | } 26 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Mocks/LocalContactsProviderMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalContactsProviderMock.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by Ioan Moldovan on 3/17/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | @testable import FlowCrypt 10 | 11 | final class LocalContactsProviderMock: LocalContactsProviderType { 12 | 13 | func searchRecipient(with email: String) async throws -> RecipientWithSortedPubKeys? { nil } 14 | 15 | func searchRecipients(query: String) throws -> [Recipient] { [] } 16 | 17 | func remove(recipient: RecipientWithSortedPubKeys) throws {} 18 | 19 | func updateKeys(for recipient: RecipientWithSortedPubKeys) throws {} 20 | 21 | func getAllRecipients() async throws -> [RecipientWithSortedPubKeys] { [] } 22 | 23 | var retrievePubKeysResult: ((String) -> ([PubKey]))! 24 | func retrievePubKeys(for email: String, shouldUpdateLastUsed: Bool) -> [PubKey] { 25 | retrievePubKeysResult(email) 26 | } 27 | 28 | func removePubKey(with fingerprint: String, for email: String) {} 29 | 30 | func updateKey(for email: String, pubKey: FlowCrypt.PubKey) throws {} 31 | } 32 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Mocks/MessageGatewayMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageGatewayMock.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by Anton Kharchevskyi on 25.07.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | @testable import FlowCrypt 10 | 11 | class MessageGatewayMock: MessageGateway { 12 | var sendMailResult: ((Data) -> (Result))! 13 | func sendMail(input: MessageGatewayInput, progressHandler: ((Float) -> Void)?) async throws -> Identifier { 14 | if case let .failure(error) = sendMailResult(input.mime) { 15 | throw error 16 | } 17 | return .random 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Mocks/MockError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockError.swift 3 | // FlowCryptAppTests 4 | // 5 | // Created by luke on 10/11/2021 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class MockError: Error {} 12 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Models Parsing/client_configuraion.json: -------------------------------------------------------------------------------- 1 | { 2 | "flags": ["NO_PRV_BACKUP", "NO_KEY_MANAGER_PUB_LOOKUP"], 3 | "custom_keyserver_url": "https://hello", 4 | "key_manager_url": "https://there", 5 | "allow_attester_search_only_for_domains": [], 6 | "disallow_attester_search_for_domains": [], 7 | "enforce_keygen_algo": "curve25519", 8 | "enforce_keygen_expire_months": 12 9 | } 10 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Models Parsing/client_configuraion_empty.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Models Parsing/client_configuraion_partly_empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "flags": ["NO_PRV_BACKUP", "NO_KEY_MANAGER_PUB_LOOKUP"], 3 | "enforce_keygen_algo": "curve25519", 4 | "enforce_keygen_expire_months": 12 5 | } 6 | -------------------------------------------------------------------------------- /FlowCryptAppTests/Models Parsing/client_configuraion_with_unknown_flag.json: -------------------------------------------------------------------------------- 1 | { 2 | "flags": ["NO_PRV_BACKUP", "NO_KEY_MANAGER_PUB_LOOKUP", "ANY_RANDOM_NEW_FLAG_THAT_IS_NOT_PRESENT_IN_FLAGS_ENUM"], 3 | "custom_keyserver_url": "https://hello", 4 | "key_manager_url": "https://there", 5 | "allow_attester_search_only_for_domains": [], 6 | "disallow_attester_search_for_domains": [], 7 | "enforce_keygen_algo": "curve25519", 8 | "enforce_keygen_expire_months": 12 9 | } 10 | -------------------------------------------------------------------------------- /FlowCryptAppTests/TestTimer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestTimer.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Ioan Moldovan on 4/6/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import CoreFoundation 10 | 11 | class TestTimer { 12 | 13 | private var startTime: CFAbsoluteTime? 14 | private var endTime: CFAbsoluteTime? 15 | 16 | func start() { 17 | startTime = CFAbsoluteTimeGetCurrent() 18 | } 19 | 20 | func stop() { 21 | endTime = CFAbsoluteTimeGetCurrent() 22 | } 23 | 24 | var durationMs: Double { 25 | if let startTime, let endTime { 26 | return (endTime - startTime) * 1000 27 | } 28 | return 0 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/ASButtonNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASButtonNode.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Ioan Moldovan on 7/14/23 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public extension ASButtonNode { 12 | private func imageWithColor(color: UIColor) -> UIImage? { 13 | let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0) 14 | UIGraphicsBeginImageContext(rect.size) 15 | let context = UIGraphicsGetCurrentContext() 16 | 17 | context?.setFillColor(color.cgColor) 18 | context?.fill(rect) 19 | 20 | let image = UIGraphicsGetImageFromCurrentImageContext() 21 | UIGraphicsEndImageContext() 22 | 23 | return image 24 | } 25 | 26 | func setBackgroundColor(_ color: UIColor, forState controlState: UIControl.State) { 27 | setBackgroundImage(imageWithColor(color: color), for: controlState) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/CalendarExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarExtensions.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Yevhen Kyivskyi on 01.06.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Calendar { 12 | func isDateInCurrentYear(_ date: Date) -> Bool { 13 | self.isDate(date, equalTo: Date(), toGranularity: .year) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/Data/DataExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // © 2017-2019 FlowCrypt Limited. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | public extension Data { 8 | func decodeJson(as _: T.Type) throws -> T where T: Decodable { 9 | try JSONDecoder().decode(T.self, from: self) 10 | } 11 | 12 | func toStr() -> String { 13 | String(decoding: self, as: UTF8.self) 14 | } 15 | 16 | func toDict() throws -> [String: Any] { 17 | try JSONSerialization.jsonObject(with: self, options: []) as? [String: Any] ?? [:] 18 | } 19 | } 20 | 21 | public extension NSMutableData { 22 | func append(_ string: String) { 23 | guard let data = string.data(using: .utf8) else { return } 24 | self.append(data) 25 | } 26 | } 27 | 28 | extension String { 29 | init(data: Data) { 30 | self = String(decoding: data, as: UTF8.self) 31 | } 32 | } 33 | 34 | public extension [Data] { 35 | var joined: Data { 36 | reduce(Data(), +) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/DateFormattingExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormattingExtensions.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 01.10.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension DateFormatter { 12 | func formatDate(_ date: Date) -> String { 13 | let dateFormater = DateFormatter() 14 | if Calendar.current.isDateInToday(date) { 15 | dateFormater.dateFormat = "HH:mm" 16 | } else if Calendar.current.isDateInCurrentYear(date) { 17 | dateFormater.dateFormat = "MMM dd" 18 | } else { 19 | dateFormater.dateFormat = "MMM dd, yyyy" 20 | } 21 | return dateFormater.string(from: date) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/Either.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Either.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Anton Kharchevskyi on 24/04/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum Either { 12 | case left(A) 13 | case right(B) 14 | } 15 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/EncodableExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // © 2017-2019 FlowCrypt Limited. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | public extension Encodable { 8 | func toJsonData() throws -> Data { 9 | try JSONEncoder().encode(self) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/ErrorExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorExtensions.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 04/11/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Error { 12 | var errorMessage: String { 13 | switch self { 14 | case let self as CustomStringConvertible: 15 | return String(describing: self) 16 | default: 17 | return localizedDescription 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/IntExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntExtensions.swift 3 | // FlowCrypt 4 | // 5 | // Created by luke on 8/1/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Int { 12 | func toDate() -> Date { 13 | Date(timeIntervalSince1970: TimeInterval(self)) 14 | } 15 | } 16 | 17 | public extension Double { 18 | /// Rounds the double to decimal places value 19 | func rounded(toPlaces places: Int) -> Double { 20 | let divisor = pow(10.0, Double(places)) 21 | return (self * divisor).rounded() / divisor 22 | } 23 | 24 | func roundedString(toPlace: Int) -> String { 25 | String(format: "%.\(toPlace)f", self) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/LocalizationExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizationExtensions.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 08.10.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @inline(__always) 12 | private func localize(_ key: String) -> String { 13 | return NSLocalizedString(key, comment: "") 14 | } 15 | 16 | @inline(__always) 17 | private func LocalizedString(_ key: String) -> String { 18 | return localize(key) 19 | } 20 | 21 | public extension String { 22 | var localized: String { 23 | return LocalizedString(self) 24 | } 25 | 26 | @inline(__always) 27 | func localizeWithArguments(_ arguments: String...) -> String { 28 | String(format: localize(self), arguments: arguments) 29 | } 30 | 31 | /// use to localize plurals with Localizable.stringsdict 32 | @inline(__always) 33 | func localizePluralsWithArguments(_ arguments: Int...) -> String { 34 | String(format: localize(self), arguments: arguments) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/NotificationExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationExtension.swift 3 | // FlowCrypt 4 | // 5 | // Created by Ioan Moldovan on 10/31/24 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | public extension Notification.Name { 10 | static var reloadThreadList: Notification.Name { 11 | return .init(rawValue: "ThreadList.Reload") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/OptionalExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionalExtensions.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Ioan Moldovan on 5/5/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Optional { 12 | func ifNotNil(_ transform: (Wrapped) throws -> U) rethrows -> U? { 13 | switch self { 14 | case let .some(value): 15 | return try .some(transform(value)) 16 | case .none: 17 | return .none 18 | } 19 | } 20 | } 21 | 22 | public extension Optional where Wrapped: Collection { 23 | var isEmptyOrNil: Bool { 24 | self?.isEmpty ?? true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/SequenceExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SequenceExtension.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Ioan Moldovan on 1/23/23 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | public extension Sequence { 10 | func asyncMap( 11 | _ transform: (Element) async throws -> T 12 | ) async rethrows -> [T] { 13 | var values = [T]() 14 | 15 | for element in self { 16 | try await values.append(transform(element)) 17 | } 18 | 19 | return values 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/UIAlertControllerExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertControllerExtensions.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Anton Kharchevskyi on 07.01.2022 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIAlertController { 12 | enum PopoverPresentationStyle { 13 | case centred(UIView) 14 | case sourceView(UIView) 15 | } 16 | 17 | @discardableResult 18 | func popoverPresentation(style: PopoverPresentationStyle) -> UIAlertController { 19 | switch style { 20 | case let .centred(view): 21 | popoverPresentationController?.centredPresentation(in: view) 22 | case let .sourceView(view): 23 | popoverPresentationController?.sourceView = view 24 | } 25 | return self 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/UIApplicationExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplicationExtensions.swift 3 | // FlowCrypt 4 | // 5 | // Created by Roma Sosnovsky on 25/10/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIApplication { 12 | 13 | var currentWindow: UIWindow? { 14 | // Get connected scenes 15 | UIApplication.shared.connectedScenes 16 | // Keep only the first active, onscreen and visible `UIWindowScene` 17 | .first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) 18 | // Get its associated windows 19 | .flatMap { $0 as? UIWindowScene }?.windows 20 | // Finally, keep only the key window 21 | .first(where: \.isKeyWindow) 22 | } 23 | 24 | var isRunningTests: Bool { 25 | NSClassFromString("XCTestCase") != nil 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/UIDeviceExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDeviceExtensions.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Anton Kharchevskyi on 05.01.2022 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIDevice { 12 | static var isIpad: Bool { 13 | UIDevice.current.userInterfaceIdiom == .pad 14 | } 15 | 16 | static var isIphone: Bool { 17 | UIDevice.current.userInterfaceIdiom == .phone 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/UIEdgeInsetsExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIEdgeInsetsExtensions.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 8/30/19. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIEdgeInsets { 12 | static var side: (CGFloat) -> UIEdgeInsets { 13 | { side in UIEdgeInsets(top: side, left: side, bottom: side, right: side) } 14 | } 15 | 16 | var width: CGFloat { 17 | left + right 18 | } 19 | 20 | var height: CGFloat { 21 | top + bottom 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/UIImageExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageExtensions.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 06.11.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIImage { 12 | func tinted(_ color: UIColor) -> UIImage? { 13 | UIGraphicsBeginImageContextWithOptions(size, false, scale) 14 | defer { UIGraphicsEndImageContext() } 15 | color.set() 16 | withRenderingMode(.alwaysTemplate) 17 | .draw(in: CGRect(origin: .zero, size: size)) 18 | return UIGraphicsGetImageFromCurrentImageContext() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/UIPopoverPresentationControllerExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIPopoverPresentationControllerExtensions.swift 3 | // FlowCryptCommon 4 | // 5 | // Created by Anton Kharchevskyi on 09.01.2022 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIPopoverPresentationController { 12 | func centredPresentation(in view: UIView) { 13 | sourceView = view 14 | sourceRect = .init( 15 | x: view.bounds.midX, 16 | y: view.bounds.midY, 17 | width: 0, 18 | height: 0 19 | ) 20 | permittedArrowDirections = [] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FlowCryptCommon/Extensions/UIViewExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // © 2017-2019 FlowCrypt Limited. All rights reserved. 3 | // 4 | 5 | import UIKit 6 | 7 | public extension UIView { 8 | func constrainToEdges(_ subview: UIView, insets: UIEdgeInsets = .zero) { 9 | subview.translatesAutoresizingMaskIntoConstraints = false 10 | 11 | let guide = self.safeAreaLayoutGuide 12 | subview.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: -insets.right).isActive = true 13 | subview.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: insets.left).isActive = true 14 | subview.topAnchor.constraint(equalTo: guide.topAnchor, constant: insets.top).isActive = true 15 | subview.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -insets.bottom).isActive = true 16 | } 17 | } 18 | 19 | // MARK: - UITextField 20 | 21 | public extension UITextField { 22 | func setTextInset(_ left: CGFloat = 7) { 23 | leftView = UIView(frame: CGRect(x: 0, y: 0, width: left, height: frame.size.height)) 24 | leftViewMode = .always 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FlowCryptCommon/FlowCryptCommon.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlowCryptCommon.h 3 | // FlowCryptCommon 4 | // 5 | // Created by Anton Kharchevskyi on 20/02/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FlowCryptCommon. 12 | FOUNDATION_EXPORT double FlowCryptCommonVersionNumber; 13 | 14 | //! Project version string for FlowCryptCommon. 15 | FOUNDATION_EXPORT const unsigned char FlowCryptCommonVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /FlowCryptCommon/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlowCryptCommon/Trace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Trace.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 02.05.2021. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import QuartzCore 10 | 11 | public final class Trace { 12 | private let id: String 13 | private var startTime: TimeInterval? 14 | 15 | public init(id: String) { 16 | self.id = id 17 | self.startTime = CACurrentMediaTime() 18 | } 19 | 20 | public func result() -> TimeInterval { 21 | guard let startTime else { 22 | return 0 23 | } 24 | let endTime = CACurrentMediaTime() 25 | 26 | return endTime - startTime 27 | } 28 | 29 | public func finish(roundedTo: Int = 3) -> String { 30 | let resultValue = result() 31 | 32 | let timeValue = resultValue <= 1 ? " ms" : " sec" 33 | 34 | return resultValue.roundedString(toPlace: roundedTo) + timeValue 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FlowCryptUI/Cell Nodes/BackupCellNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackupCellNode.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Anton Kharchevskyi on 23/09/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class BackupCellNode: CellNode { 12 | private let textNode = ASTextNode2() 13 | private let insets: UIEdgeInsets 14 | 15 | public init(title: NSAttributedString, insets: UIEdgeInsets) { 16 | self.textNode.attributedText = title 17 | self.insets = insets 18 | } 19 | 20 | override public func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 21 | return ASCenterLayoutSpec( 22 | centeringOptions: .XY, 23 | sizingOptions: .minimumXY, 24 | child: ASInsetLayoutSpec( 25 | insets: insets, 26 | child: textNode 27 | ) 28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FlowCryptUI/Cell Nodes/CellNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellNode.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 31.10.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | open class CellNode: ASCellNode { 12 | var leftBorder: ASDisplayNode? 13 | 14 | func addLeftBorder(width: CGFloat, color: UIColor?) { 15 | let border = ASDisplayNode() 16 | border.backgroundColor = color 17 | border.style.width = ASDimension(unit: .points, value: width) 18 | border.style.height = ASDimension(unit: .fraction, value: 1) 19 | addSubnode(border) 20 | leftBorder = border 21 | } 22 | 23 | override public func layout() { 24 | super.layout() 25 | let leftBorderWidth = leftBorder?.style.width.value ?? 0 26 | leftBorder?.frame = CGRect(x: 0, y: 0, width: leftBorderWidth, height: self.bounds.height) 27 | } 28 | 29 | override public init() { 30 | super.init() 31 | automaticallyManagesSubnodes = true 32 | selectionStyle = .none 33 | backgroundColor = .backgroundColor 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /FlowCryptUI/Cell Nodes/DividerCellNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DividerCellNode.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 31.10.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class DividerCellNode: CellNode { 12 | private let line = ASDisplayNode() 13 | private let inset: UIEdgeInsets 14 | 15 | public init( 16 | inset: UIEdgeInsets = .zero, 17 | color: UIColor = .lightGray, 18 | height: CGFloat = 0.5 19 | ) { 20 | self.inset = inset 21 | super.init() 22 | line.style.preferredSize.height = height 23 | line.backgroundColor = color 24 | backgroundColor = .clear 25 | } 26 | 27 | override public func layoutSpecThatFits(_ range: ASSizeRange) -> ASLayoutSpec { 28 | let expectedWidth = range.max.width - inset.width 29 | line.style.preferredSize.width = expectedWidth > 0 ? expectedWidth : range.max.width 30 | return ASInsetLayoutSpec(insets: inset, child: line) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowCryptUI/Cell Nodes/MenuSeparatorCellNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuSeparatorCellNode.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Ioan Moldovan on 7/13/23 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | open class MenuSeparatorCellNode: ASCellNode { 12 | let separatorView: ASDisplayNode = { 13 | let node = ASDisplayNode() 14 | node.backgroundColor = .separator 15 | node.style.flexGrow = 1.0 16 | node.style.preferredSize.height = 0.5 17 | return node 18 | }() 19 | 20 | override public init() { 21 | super.init() 22 | addSubnode(separatorView) 23 | } 24 | 25 | override public func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 26 | return ASInsetLayoutSpec(insets: .deviceSpecificTextInsets(top: 0, bottom: 0), child: separatorView) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FlowCryptUI/Cell Nodes/MessageSubjectNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageSubjectNode.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Roma Sosnovsky on 05/11/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class MessageSubjectNode: CellNode { 12 | private let subjectNode = ASEditableTextNode() 13 | 14 | public init(_ subject: NSAttributedString?) { 15 | super.init() 16 | subjectNode.attributedText = subject 17 | subjectNode.maximumLinesToDisplay = 5 18 | DispatchQueue.main.async { 19 | self.subjectNode.textView.isSelectable = true 20 | self.subjectNode.textView.isEditable = false 21 | } 22 | } 23 | 24 | override public func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { 25 | subjectNode.style.flexGrow = 1.0 26 | return ASInsetLayoutSpec( 27 | insets: .deviceSpecificTextInsets(top: 16, bottom: 4), 28 | child: subjectNode 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FlowCryptUI/Cell Nodes/RecipientEmailTextFieldNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecipientEmailTextFieldNode.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Roma Sosnovsky on 10/02/22 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class RecipientEmailTextFieldNode: TextFieldCellNode { 12 | 13 | override public init( 14 | input: TextFieldCellNode.Input, 15 | action: TextFieldAction? = nil 16 | ) { 17 | super.init(input: input, action: action) 18 | 19 | self.isLowercased = true 20 | } 21 | 22 | @discardableResult 23 | override public func becomeFirstResponder() -> Bool { 24 | textField.becomeFirstResponder() 25 | return true 26 | } 27 | 28 | override public func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { 29 | textField.style.preferredSize.height = input.height 30 | 31 | return ASInsetLayoutSpec(insets: input.insets, child: textField) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /FlowCryptUI/Cell Nodes/TitleCellNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TitleCellNode.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 12/9/19. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class TitleCellNode: CellNode { 12 | private let textNode = ASTextNode2() 13 | private let insets: UIEdgeInsets 14 | 15 | public init(title: NSAttributedString, insets: UIEdgeInsets) { 16 | self.insets = insets 17 | super.init() 18 | textNode.attributedText = title 19 | } 20 | 21 | override public func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { 22 | ASInsetLayoutSpec( 23 | insets: insets, 24 | child: textNode 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FlowCryptUI/FlowCryptUI.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlowCryptUI.h 3 | // FlowCryptUI 4 | // 5 | // Created by Anton Kharchevskyi on 17/02/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // Project version number for FlowCryptUI. 12 | FOUNDATION_EXPORT double FlowCryptUIVersionNumber; 13 | 14 | // Project version string for FlowCryptUI. 15 | FOUNDATION_EXPORT const unsigned char FlowCryptUIVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | -------------------------------------------------------------------------------- /FlowCryptUI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/AddButtonNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddButtonNode.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 01.10.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class AddButtonNode: ASButtonNode { 12 | private var onTap: (() -> Void)? 13 | 14 | public init(identifier: String, _ action: (() -> Void)?) { 15 | super.init() 16 | onTap = action 17 | backgroundColor = .main 18 | accessibilityIdentifier = identifier 19 | setTitle("+", with: .boldSystemFont(ofSize: 30), with: .white, for: .normal) 20 | addTarget(self, action: #selector(onButtonTap), forControlEvents: .touchUpInside) 21 | frame.size = CGSize(width: .addButtonSize, height: .addButtonSize) 22 | cornerRadius = .addButtonSize / 2 23 | } 24 | 25 | @objc private func onButtonTap() { 26 | onTap?() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/ButtonNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonNode.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 29.10.2019 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class ButtonNode: ASButtonNode { 12 | private var onTap: (() -> Void)? 13 | 14 | public init(_ action: (() -> Void)?) { 15 | onTap = action 16 | super.init() 17 | addTarget(self, action: #selector(handleTap), forControlEvents: .touchUpInside) 18 | } 19 | 20 | @objc private func handleTap() { 21 | onTap?() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/ButtonWithPaddingNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonWithPaddingNode.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Roma Sosnovsky on 15/12/21 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class ButtonWithPaddingNode: ASDisplayNode { 12 | private let insets: UIEdgeInsets 13 | private let buttonNode = ASTextNode2() 14 | 15 | public init( 16 | text: NSAttributedString?, 17 | insets: UIEdgeInsets, 18 | backgroundColor: UIColor? = nil, 19 | cornerRadius: CGFloat = 0, 20 | action: (() -> Void)? 21 | ) { 22 | self.insets = insets 23 | 24 | super.init() 25 | 26 | automaticallyManagesSubnodes = true 27 | buttonNode.attributedText = text 28 | 29 | self.backgroundColor = backgroundColor 30 | self.cornerRadius = cornerRadius 31 | } 32 | 33 | override public func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { 34 | ASInsetLayoutSpec( 35 | insets: insets, 36 | child: buttonNode 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/CheckBoxNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckBoxNode.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Anton Kharchevskyi on 27/09/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class CheckBoxNode: ASDisplayNode { 12 | public struct Input { 13 | let color: UIColor 14 | let strokeWidth: CGFloat 15 | 16 | public init( 17 | color: UIColor, 18 | strokeWidth: CGFloat 19 | ) { 20 | self.color = color 21 | self.strokeWidth = strokeWidth 22 | } 23 | } 24 | 25 | public convenience init(_ input: Input) { 26 | self.init { () -> UIView in 27 | let view = CheckBoxCircleView() 28 | view.innerColor = input.color 29 | view.outerColor = input.color 30 | view.innerInset = 4 31 | view.strokeWidth = input.strokeWidth 32 | view.backgroundColor = .clear 33 | return view 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/KeyTextCellNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyTextCellNode.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 12/13/19. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class KeyTextCellNode: CellNode { 12 | 13 | private let insets: UIEdgeInsets 14 | private let textNode = ASTextNode2() 15 | 16 | public init( 17 | title: NSAttributedString, 18 | insets: UIEdgeInsets 19 | ) { 20 | self.insets = insets 21 | super.init() 22 | textNode.attributedText = title 23 | } 24 | 25 | override public func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { 26 | ASInsetLayoutSpec( 27 | insets: insets, 28 | child: textNode 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/SignInDescriptionNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignInDescriptionNode.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 23.10.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class SignInDescriptionNode: CellNode { 12 | private let textNode = ASTextNode2() 13 | 14 | public init(_ title: NSAttributedString?) { 15 | super.init() 16 | textNode.attributedText = title 17 | textNode.accessibilityLabel = "description" 18 | automaticallyManagesSubnodes = true 19 | selectionStyle = .none 20 | } 21 | 22 | override public func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { 23 | return ASInsetLayoutSpec( 24 | insets: UIEdgeInsets(top: 30, left: 16, bottom: 55, right: 16), 25 | child: ASCenterLayoutSpec( 26 | centeringOptions: .XY, 27 | sizingOptions: .minimumXY, 28 | child: textNode 29 | ) 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/SignInImageNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignInImageNode.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 23.10.2019. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | import CoreServices 11 | import UIKit 12 | 13 | public final class SignInImageNode: CellNode { 14 | private let imageNode = ASImageNode() 15 | private let imageHeight: CGFloat 16 | 17 | public init(_ image: UIImage?, imageHeight: CGFloat) { 18 | self.imageHeight = imageHeight 19 | super.init() 20 | imageNode.image = image 21 | imageNode.contentMode = .scaleAspectFit 22 | addSubnode(imageNode) 23 | setNeedsLayout() 24 | } 25 | 26 | override public func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { 27 | imageNode.style.preferredSize.height = imageHeight 28 | 29 | return ASInsetLayoutSpec( 30 | insets: .deviceSpecificInsets( 31 | top: UIDevice.isIphone ? 8 : 32, 32 | bottom: 0 33 | ), 34 | child: imageNode 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/ToggleQuoteButtonNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToggleQuoteButtonNode.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Ioan Moldovan on 11/28/23 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | 11 | public final class ToggleQuoteButtonNode: ASButtonNode { 12 | private let onTap: (() -> Void)? 13 | 14 | public init(index: Int, onTap: (() -> Void)? = nil) { 15 | self.onTap = onTap 16 | 17 | super.init() 18 | 19 | let configuration = UIImage.SymbolConfiguration(pointSize: 16, weight: .ultraLight) 20 | let image = UIImage(systemName: "ellipsis", withConfiguration: configuration) 21 | cornerRadius = 4 22 | borderColor = UIColor.main.cgColor 23 | borderWidth = 1 24 | accessibilityIdentifier = "aid-message-\(index)-quote-toggle" 25 | setImage(image, for: .normal) 26 | contentEdgeInsets = .side(4) 27 | imageNode.imageModificationBlock = ASImageNodeTintColorModificationBlock(.main) 28 | addTarget(self, action: #selector(onToggleQuoteButtonTap), forControlEvents: .touchUpInside) 29 | } 30 | 31 | @objc private func onToggleQuoteButtonTap() { 32 | onTap?() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FlowCryptUI/Nodes/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Anton Kharchevskyi on 11.01.2022 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | import FlowCryptCommon 11 | 12 | @MainActor 13 | open class ViewController: ASDKViewController { 14 | override open func viewDidLoad() { 15 | super.viewDidLoad() 16 | Logger.nested(Self.self).logDebug("View did load") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FlowCryptUI/Resources/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Anton Kharchevskyi on 19/02/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import AsyncDisplayKit 10 | import LetterAvatarKit 11 | import UIKit 12 | 13 | public func testAttributedText() -> NSAttributedString { 14 | let count = Int.random(in: 10 ... 30) 15 | return NSAttributedString( 16 | string: String((5 ... count).compactMap { _ in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".randomElement() }), 17 | attributes: [ 18 | NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14), 19 | NSAttributedString.Key.foregroundColor: UIColor.black 20 | ] 21 | ) 22 | } 23 | 24 | public func getAvatarImage(text: String) -> ASImageNode { 25 | let node = ASImageNode() 26 | let avatarImage = LetterAvatarMaker() 27 | .setCircle(true) 28 | .setUsername(text.capitalized) 29 | .build() 30 | node.image = avatarImage 31 | node.style.preferredSize.width = .Avatar.width 32 | node.style.preferredSize.height = .Avatar.height 33 | return node 34 | } 35 | -------------------------------------------------------------------------------- /FlowCryptUI/Views/CheckBoxCircleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckBoxCircleView.swift 3 | // FlowCryptUI 4 | // 5 | // Created by Anton Kharchevskyi on 27/09/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CheckBoxCircleView: UIView { 12 | var strokeWidth: CGFloat = 2 { didSet { setNeedsDisplay() } } 13 | var innerInset: CGFloat = 10 { didSet { setNeedsDisplay() } } 14 | 15 | var outerColor: UIColor = .red { didSet { setNeedsDisplay() } } 16 | var innerColor: UIColor = .green { didSet { setNeedsDisplay() } } 17 | 18 | override func draw(_ rect: CGRect) { 19 | let outerPath = UIBezierPath( 20 | ovalIn: rect.insetBy(dx: strokeWidth, dy: strokeWidth) 21 | ) 22 | outerColor.setStroke() 23 | outerPath.lineWidth = strokeWidth 24 | outerPath.stroke() 25 | 26 | let dx = innerInset + strokeWidth 27 | let innerPath = UIBezierPath( 28 | ovalIn: rect.insetBy(dx: dx, dy: dx) 29 | ) 30 | innerColor.setFill() 31 | innerPath.fill() 32 | 33 | backgroundColor = .clear 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /FlowCryptUI/Views/NavigationBarActionButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationBarActionButton.swift 3 | // FlowCrypt 4 | // 5 | // Created by Anton Kharchevskyi on 8/23/19. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class NavigationBarActionButton: UIBarButtonItem { 12 | private enum Constants { 13 | static let buttonSize = CGSize(width: 44, height: 44) 14 | } 15 | 16 | private var onAction: (() -> Void)? 17 | 18 | public convenience init(imageSystemName: String, action: (() -> Void)?, accessibilityIdentifier: String? = nil) { 19 | self.init() 20 | onAction = action 21 | customView = UIButton(type: .system).with { 22 | $0.contentHorizontalAlignment = .left 23 | $0.setImage(UIImage(systemName: imageSystemName), for: .normal) 24 | $0.frame.size = Constants.buttonSize 25 | $0.addTarget(self, action: #selector(tap), for: .touchUpInside) 26 | $0.accessibilityIdentifier = accessibilityIdentifier 27 | $0.isAccessibilityElement = true 28 | } 29 | } 30 | 31 | @objc private func tap() { 32 | onAction?() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FlowCryptUIApplication/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FlowCryptUIApplication 4 | // 5 | // Created by Anton Kharchevskyi on 19/02/2020. 6 | // Copyright © 2017-present FlowCrypt a. s. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | window = UIWindow(frame: UIScreen.main.bounds) 17 | window?.rootViewController = ViewController() 18 | window?.makeKeyAndVisible() 19 | return true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlowCryptUIApplication/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/Colors.xcassets/activityIndicatorColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "platform" : "ios", 11 | "reference" : "systemGrayColor" 12 | } 13 | }, 14 | { 15 | "idiom" : "universal", 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "red" : "1.000", 26 | "alpha" : "1.000", 27 | "blue" : "1.000", 28 | "green" : "1.000" 29 | } 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/Colors.xcassets/additionalInfoLabelColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.224", 13 | "alpha" : "1.000", 14 | "blue" : "0.224", 15 | "green" : "0.224" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/Colors.xcassets/backgroundColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "1.000", 13 | "alpha" : "1.000", 14 | "blue" : "1.000", 15 | "green" : "1.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "platform" : "ios", 29 | "reference" : "systemGray5Color" 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/Colors.xcassets/dividerColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "display-p3", 11 | "components" : { 12 | "red" : "1.000", 13 | "alpha" : "0.100", 14 | "blue" : "1.000", 15 | "green" : "1.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "extended-gray", 29 | "components" : { 30 | "white" : "0.400", 31 | "alpha" : "1.000" 32 | } 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/Colors.xcassets/mainGreenColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.129", 13 | "alpha" : "1.000", 14 | "blue" : "0.020", 15 | "green" : "0.616" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/Colors.xcassets/mainTextColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.000", 13 | "alpha" : "1.000", 14 | "blue" : "0.000", 15 | "green" : "0.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "1.000", 31 | "alpha" : "1.000", 32 | "blue" : "1.000", 33 | "green" : "1.000" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/Colors.xcassets/mainTextUnreadColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.000", 13 | "alpha" : "1.000", 14 | "blue" : "0.000", 15 | "green" : "0.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "1.000", 31 | "alpha" : "1.000", 32 | "blue" : "1.000", 33 | "green" : "1.000" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /FlowCryptUIApplication/README.md: -------------------------------------------------------------------------------- 1 | # FlowCryptUI test application 2 | 3 | ## Test application for easy testing UI common nodes 4 | 5 | All common UI elements are located in `FlowCryptUI` target. 6 | This target also include `Texture` pod. 7 | Every build of this app will also build a FlowCryptUI target. 8 | 9 | UI elements (Nodes and Views) are added in ViewController as a separate cells in table view. 10 | Which is a root controller of this test app 11 | 12 | ## Usage 13 | * Choose FlowCryptUIApplication in schemes of the application and run it on simulator 14 | * You can add elements into `ViewController` 15 | * Just add case into Elements enum and return proper node for this UIElement 16 | 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem "fastlane", "2.227.2" 4 | gem "abbrev" 5 | gem "mutex_m" 6 | gem "ostruct" -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: ui_tests 3 | 4 | dependencies: 5 | gem install bundler:2.3.24 6 | bundle config set path 'vendor/bundle' 7 | bundle install 8 | 9 | ui_tests: dependencies 10 | bundle exec fastlane test_ui --verbose 11 | ui_tests_gmail: dependencies 12 | bundle exec fastlane test_ui_gmail --verbose 13 | ui_tests_imap: dependencies 14 | bundle exec fastlane test_ui_imap --verbose 15 | 16 | snapshots: dependencies 17 | brew update && brew install imagemagick 18 | bundle exec fastlane snapshot 19 | cd fastlane/screenshots 20 | fastlane frameit -------------------------------------------------------------------------------- /Scripts/generate-icons.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail # debug + fail when any command fails 4 | 5 | sizes=(20 29 40 50 57 58 60 72 76 80 87 100 114 120 144 152 167 180 1024) 6 | 7 | for size in "${sizes[@]}" 8 | do 9 | # Default 10 | rsvg-convert -w $size -h $size --background-color white ./asset-sources/icons/flowcrypt-ios-svg.svg > ./FlowCrypt/Assets.xcassets/Appicon.appiconset/$size.png 11 | # Enterprise 12 | rsvg-convert -w $size -h $size --background-color white ./asset-sources/icons/flowcrypt-ios-enterprise-svg.svg > ./FlowCrypt/Assets.xcassets/Appicon-Enterprise.appiconset/$size.png 13 | # Debug 14 | rsvg-convert -w $size -h $size --background-color white ./asset-sources/icons/flowcrypt-ios-debug-svg.svg > ./FlowCrypt/Assets.xcassets/Appicon-Debug.appiconset/$size.png 15 | done 16 | 17 | -------------------------------------------------------------------------------- /Scripts/generate-mock-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | IP=$(echo $1 | egrep -o "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$") 4 | 5 | if [ ! $IP ] 6 | then 7 | echo "Usage: generate-ip-cert.sh 127.0.0.1" 8 | exit 1 9 | fi 10 | 11 | echo "[req] 12 | default_bits = 2048 13 | distinguished_name = req_distinguished_name 14 | req_extensions = req_ext 15 | x509_extensions = v3_req 16 | prompt = no 17 | 18 | [req_distinguished_name] 19 | countryName = XX 20 | stateOrProvinceName = N/A 21 | localityName = N/A 22 | organizationName = Self-signed certificate 23 | commonName = $IP: Self-signed certificate 24 | 25 | [req_ext] 26 | subjectAltName = @alt_names 27 | 28 | [v3_req] 29 | subjectAltName = @alt_names 30 | 31 | [alt_names] 32 | IP.1 = $IP 33 | " > local.cnf 34 | 35 | openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout key.pem.mock -out cert.pem.mock -config local.cnf 36 | rm local.cnf 37 | mv key.pem.mock cert.pem.mock ./appium/api-mocks/mock-ssl-cert -------------------------------------------------------------------------------- /appium/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | config/ -------------------------------------------------------------------------------- /appium/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.eslint.json', 5 | tsconfigRootDir: __dirname, 6 | }, 7 | plugins: ['@typescript-eslint'], 8 | extends: [ 9 | 'eslint:recommended', 10 | 'plugin:@typescript-eslint/eslint-recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | 'prettier', 13 | ], 14 | rules: { 15 | '@typescript-eslint/no-unused-vars': ['error'], 16 | '@typescript-eslint/no-non-null-assertion': 'off', 17 | '@typescript-eslint/ban-ts-comment': 'off', 18 | '@typescript-eslint/no-floating-promises': ['error'], 19 | '@typescript-eslint/no-misused-promises': ['error'], 20 | '@typescript-eslint/ban-types': 'off', 21 | '@typescript-eslint/no-explicit-any': 'warn', 22 | '@typescript-eslint/no-unsafe-return': 'error', 23 | '@typescript-eslint/triple-slash-reference': 'off', 24 | '@typescript-eslint/no-empty-interface': 'off', 25 | '@typescript-eslint/no-namespace': 'off', 26 | 'no-control-regex': 'off', 27 | 'no-empty-pattern': 'off', 28 | 'no-prototype-builtins': 'off', 29 | }, 30 | env: { 31 | // change as necessary 32 | node: true, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /appium/api-mocks/core/crypto/pgp/pgp-hash.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | 'use strict'; 4 | 5 | import { Buf } from '../../buf'; 6 | import { opgp } from './openpgpjs-custom'; 7 | 8 | export class PgpHash { 9 | public static sha1UtfStr = async (string: string): Promise => { 10 | return opgp.util.Uint8Array_to_hex(await opgp.crypto.hash.digest(opgp.enums.hash.sha1, Buf.fromUtfStr(string))); 11 | }; 12 | 13 | public static sha256UtfStr = async (string: string) => { 14 | return opgp.util.Uint8Array_to_hex(await opgp.crypto.hash.digest(opgp.enums.hash.sha256, Buf.fromUtfStr(string))); 15 | }; 16 | 17 | public static doubleSha1Upper = async (string: string) => { 18 | return (await PgpHash.sha1UtfStr(await PgpHash.sha1UtfStr(string))).toUpperCase(); 19 | }; 20 | 21 | public static challengeAnswer = async (answer: string) => { 22 | return await PgpHash.cryptoHashSha256Loop(answer); 23 | }; 24 | 25 | private static cryptoHashSha256Loop = async (string: string, times = 100000) => { 26 | for (let i = 0; i < times; i++) { 27 | string = await PgpHash.sha256UtfStr(string); 28 | } 29 | return string; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /appium/api-mocks/core/stream.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | export class Stream { 4 | public static readToEnd = async (data: ReadableStream) => { 5 | let buffer = new Uint8Array(); 6 | const ws = new WritableStream({ 7 | write: chunk => { 8 | buffer = new Uint8Array([...buffer, ...chunk]); 9 | }, 10 | }); 11 | await data.pipeTo(ws); 12 | return buffer; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /appium/api-mocks/core/types/emailjs.d.ts: -------------------------------------------------------------------------------- 1 | export class MimeParser { 2 | constructor(); 3 | onheader: (node: MimeParserNode) => void; 4 | onbody: (node: MimeParserNode) => void; 5 | onend: () => void; 6 | node: MimeParserNode; // root node 7 | write: (chunk: Uint8Array | string) => void; 8 | end: () => void; 9 | } 10 | 11 | export type MimeParserNode = { 12 | path: string[]; 13 | headers: { 14 | [key: string]: { 15 | value: string; 16 | initial: string; 17 | params?: { charset?: string; filename?: string; name?: string }; 18 | }[]; 19 | }; 20 | rawContent: string | undefined; // only for content nodes (leaves) 21 | content: Uint8Array; 22 | appendChild: (child: MimeParserNode) => void; 23 | contentTransferEncoding: { value: string }; 24 | charset?: string; 25 | addHeader: (name: string, value: string) => void; 26 | raw: string; // on all nodes, not just body nodes 27 | _parentNode: MimeParserNode | null; 28 | _childNodes: MimeParserNode[] | false; 29 | _isMultipart: 'signed' | 'mixed' | false; 30 | _lineCount: number; 31 | _isRfc822: boolean; 32 | _multipartBoundary: string | false; 33 | }; 34 | -------------------------------------------------------------------------------- /appium/api-mocks/mock-config.ts: -------------------------------------------------------------------------------- 1 | import { EkmConfig, FesConfig } from './lib/configuration-types'; 2 | import { ekmKeySamples } from './apis/ekm/ekm-endpoints'; 3 | import { CommonData } from '../tests/data'; 4 | 5 | export class MockApiConfig { 6 | static get defaultEnterpriseEkmConfiguration(): EkmConfig { 7 | return { 8 | returnKeys: [ekmKeySamples.key0.prv, ekmKeySamples.e2e.prv, ekmKeySamples.key1.prv], 9 | }; 10 | } 11 | 12 | static get defaultEnterpriseFesConfiguration(): FesConfig { 13 | return { 14 | clientConfiguration: { 15 | flags: [ 16 | 'NO_PRV_CREATE', 17 | 'NO_PRV_BACKUP', 18 | 'NO_ATTESTER_SUBMIT', 19 | 'PRV_AUTOIMPORT_OR_AUTOGEN', 20 | 'FORBID_STORING_PASS_PHRASE', 21 | ], 22 | key_manager_url: CommonData.keyManagerURL.mockServer, 23 | }, 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /appium/api-mocks/platform/debug.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | 'use strict'; 4 | /* eslint-disable @typescript-eslint/no-explicit-any */ 5 | export class Debug { 6 | private static DATA: any[] = []; 7 | 8 | public static readDatabase = async (): Promise => { 9 | const old = Debug.DATA; 10 | Debug.DATA = []; 11 | return old; // eslint-disable-line @typescript-eslint/no-unsafe-return 12 | }; 13 | 14 | public static addMessage = async (message: any): Promise => { 15 | Debug.DATA.push(message); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /appium/api-mocks/platform/key-cache.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | import { Key } from '../core/crypto/key'; 4 | 5 | export class KeyCache { 6 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 | public static setDecrypted = (k: Key) => { 8 | // tests don't need this 9 | }; 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 12 | public static getDecrypted = (longid: string): Key | undefined => { 13 | return undefined; // tests don't need this 14 | }; 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 17 | public static setArmored = (armored: string, k: Key) => { 18 | // tests don't need this 19 | }; 20 | 21 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 22 | public static getArmored = (armored: string): Key | undefined => { 23 | return undefined; // tests don't need this 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /appium/api-mocks/platform/require.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | /// 4 | 5 | 'use strict'; 6 | 7 | import * as openpgp from 'openpgp'; 8 | 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | export const requireOpenpgp = (): typeof OpenPGP => { 11 | return openpgp as any as typeof OpenPGP; 12 | }; 13 | 14 | export const requireMimeParser = (): any => { 15 | // const MimeParser = (window as any)['emailjs-mime-parser'](); 16 | // return require('../../../../../extension/lib/emailjs/emailjs-mime-parser'); // todo 17 | return undefined; // the above does not work, would have to import directly from npm, but we have made custom edits to the lib so not feasible now 18 | }; 19 | 20 | export const requireMimeBuilder = (): any => { 21 | // const MimeBuilder = (window as any)['emailjs-mime-builder']; 22 | return undefined; // todo 23 | }; 24 | 25 | export const requireIso88592 = (): any => { 26 | // (window as any).iso88592 27 | return undefined; // todo 28 | }; 29 | -------------------------------------------------------------------------------- /appium/api-mocks/platform/util.ts: -------------------------------------------------------------------------------- 1 | /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ 2 | 3 | 'use strict'; 4 | 5 | import { Buf } from '../core/buf'; 6 | import { randomBytes } from 'crypto'; 7 | 8 | export const secureRandomBytes = (length: number): Uint8Array => { 9 | return randomBytes(length); 10 | }; 11 | 12 | export const base64encode = (binary: string): string => { 13 | return Buffer.from(binary, 'binary').toString('base64'); 14 | }; 15 | 16 | export const base64decode = (b64tr: string): string => { 17 | return Buffer.from(b64tr, 'base64').toString('binary'); 18 | }; 19 | 20 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 21 | export const iso2022jpToUtf = (content: Buf) => { 22 | throw new Error('iso2022jpToUtf not implemented on node'); 23 | }; 24 | -------------------------------------------------------------------------------- /appium/config/wdio.live.conf.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { config } from './wdio.shared.conf'; 3 | 4 | config.suites = { 5 | all: ['../tests/specs/live/**/*.spec.ts'], 6 | }; 7 | 8 | config.capabilities = [ 9 | { 10 | platformName: 'iOS', 11 | hostname: '127.0.0.1', 12 | 'appium:automationName': 'XCUITest', 13 | 'appium:deviceName': 'iPhone 16', 14 | 'appium:platformVersion': '18.2', 15 | 'appium:app': join(process.cwd(), './FlowCrypt.app'), 16 | }, 17 | ]; 18 | 19 | exports.config = config; 20 | -------------------------------------------------------------------------------- /appium/tests/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_TIMEOUT = 15000; 2 | -------------------------------------------------------------------------------- /appium/tests/helpers/AppiumHelper.ts: -------------------------------------------------------------------------------- 1 | import { CommonData } from '../data'; 2 | 3 | class AppiumHelper { 4 | /** 5 | * Restart app with processArguments 6 | */ 7 | static async restartApp(processArgs: string[] = []) { 8 | const bundleId = CommonData.bundleId.id; 9 | await driver.terminateApp(bundleId); 10 | const args = { 11 | bundleId, 12 | arguments: processArgs, 13 | }; 14 | await driver.execute('mobile: launchApp', args); 15 | } 16 | } 17 | 18 | export default AppiumHelper; 19 | -------------------------------------------------------------------------------- /appium/tests/helpers/DataHelper.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | class DataHelper { 4 | static uniqueValue() { 5 | return Math.random().toString(36).substring(2); 6 | } 7 | static convertStringToDate = (date: string) => { 8 | return moment(date.replace('at', '')).utcOffset(0); 9 | }; 10 | } 11 | 12 | export default DataHelper; 13 | -------------------------------------------------------------------------------- /appium/tests/screenobjects/all-screens.ts: -------------------------------------------------------------------------------- 1 | import SplashScreen from './splash.screen'; 2 | import SetupKeyScreen from './setup-key.screen'; 3 | import MenuBarScreen from './menu-bar.screen'; 4 | import EmailScreen from './email.screen'; 5 | import NewMessageScreen from './new-message.screen'; 6 | import SettingsScreen from './settings.screen'; 7 | import KeysScreen from './keys.screen'; 8 | import ContactScreen from './contacts.screen'; 9 | import ContactPublicKeyScreen from './contact-public-key.screen'; 10 | import PublicKeyScreen from './public-key.screen'; 11 | import AttachmentScreen from './attachment.screen'; 12 | import MailFolderScreen from './mail-folder.screen'; 13 | import SearchScreen from './search.screen'; 14 | import EmailProviderScreen from './email-provider.screen'; 15 | import PublicKeyDetailsScreen from './public-key-details.screen'; 16 | 17 | export { 18 | SplashScreen, 19 | SetupKeyScreen, 20 | MenuBarScreen, 21 | EmailScreen, 22 | NewMessageScreen, 23 | SettingsScreen, 24 | KeysScreen, 25 | AttachmentScreen, 26 | PublicKeyScreen, 27 | ContactScreen, 28 | ContactPublicKeyScreen, 29 | MailFolderScreen, 30 | SearchScreen, 31 | EmailProviderScreen, 32 | PublicKeyDetailsScreen, 33 | }; 34 | -------------------------------------------------------------------------------- /appium/tests/screenobjects/settings.screen.ts: -------------------------------------------------------------------------------- 1 | import BaseScreen from './base.screen'; 2 | import ElementHelper from '../helpers/ElementHelper'; 3 | const SELECTORS = { 4 | MENU_ICON: '~aid-menu-button', 5 | }; 6 | 7 | class SettingsScreen extends BaseScreen { 8 | constructor() { 9 | super(SELECTORS.MENU_ICON); 10 | } 11 | 12 | settingsItem = async (setting: string) => { 13 | return await $(`~${setting}`); 14 | }; 15 | 16 | checkSettingsScreen = async () => { 17 | await (await this.settingsItem('Security and Privacy')).waitForDisplayed(); 18 | await (await this.settingsItem('Contacts')).waitForDisplayed(); 19 | await (await this.settingsItem('Keys')).waitForDisplayed(); 20 | await (await this.settingsItem('Attester')).waitForDisplayed(); 21 | await (await this.settingsItem('Notifications')).waitForDisplayed(); 22 | await (await this.settingsItem('Legal')).waitForDisplayed(); 23 | await (await this.settingsItem('Experimental')).waitForDisplayed(); 24 | await (await this.settingsItem('Backups')).waitForDisplayed({ reverse: true }); 25 | }; 26 | 27 | clickOnSettingItem = async (item: string) => { 28 | await ElementHelper.waitAndClick(await this.settingsItem(item)); 29 | }; 30 | } 31 | 32 | export default new SettingsScreen(); 33 | -------------------------------------------------------------------------------- /appium/tests/specs/mock/composeEmail/CheckRecipientEvaluationWhenTapAway.spec.ts: -------------------------------------------------------------------------------- 1 | import { MockApi } from 'api-mocks/mock'; 2 | import { MockApiConfig } from 'api-mocks/mock-config'; 3 | import { MailFolderScreen, NewMessageScreen, SetupKeyScreen, SplashScreen } from '../../../screenobjects/all-screens'; 4 | 5 | describe('COMPOSE EMAIL: ', () => { 6 | it('check recipient evaluation when user taps outside the search area', async () => { 7 | const mockApi = new MockApi(); 8 | 9 | mockApi.fesConfig = MockApiConfig.defaultEnterpriseFesConfiguration; 10 | mockApi.ekmConfig = MockApiConfig.defaultEnterpriseEkmConfiguration; 11 | 12 | await mockApi.withMockedApis(async () => { 13 | await SplashScreen.mockLogin(); 14 | await SetupKeyScreen.setPassPhrase(); 15 | await MailFolderScreen.checkInboxScreen(); 16 | 17 | await MailFolderScreen.clickCreateEmail(); 18 | 19 | await NewMessageScreen.checkRecipientEvaluationWhenTapOutside(); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /appium/tests/specs/mock/login/CancelLoginAndLogout.spec.ts: -------------------------------------------------------------------------------- 1 | import { MockApi } from 'api-mocks/mock'; 2 | import { MockApiConfig } from 'api-mocks/mock-config'; 3 | import { SplashScreen, SetupKeyScreen, MenuBarScreen, MailFolderScreen } from '../../../screenobjects/all-screens'; 4 | 5 | describe('LOGIN: ', () => { 6 | it('user should be able to cancel login + login + logout', async () => { 7 | const mockApi = new MockApi(); 8 | 9 | mockApi.fesConfig = MockApiConfig.defaultEnterpriseFesConfiguration; 10 | mockApi.ekmConfig = MockApiConfig.defaultEnterpriseEkmConfiguration; 11 | 12 | await mockApi.withMockedApis(async () => { 13 | await SplashScreen.clickContinueWithGmail(); 14 | await SplashScreen.clickCancelButton(); 15 | await SplashScreen.checkLoginPage(); 16 | 17 | await SplashScreen.mockLogin(); 18 | await SetupKeyScreen.setPassPhrase(); 19 | await MailFolderScreen.checkInboxScreen(); 20 | 21 | await MenuBarScreen.clickMenuBtn(); 22 | await MenuBarScreen.checkUserEmail(); 23 | 24 | await MenuBarScreen.clickLogout(); 25 | await SplashScreen.checkLoginPage(); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /appium/tests/specs/mock/setup/SetupFailsWithInconsistentClientConfiguration.spec.ts: -------------------------------------------------------------------------------- 1 | import { MockApi } from 'api-mocks/mock'; 2 | import { SplashScreen } from '../../../screenobjects/all-screens'; 3 | import BaseScreen from '../../../screenobjects/base.screen'; 4 | 5 | describe('SETUP: ', () => { 6 | it('app setup fails with bad EKM URL', async () => { 7 | const mockApi = new MockApi(); 8 | mockApi.fesConfig = { 9 | clientConfiguration: { 10 | key_manager_url: 'INTENTIONAL BAD URL', 11 | }, 12 | }; 13 | await mockApi.withMockedApis(async () => { 14 | await SplashScreen.mockLogin(); 15 | await BaseScreen.checkModalMessage('Error\n' + 'Please check if key manager url set correctly'); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /appium/tests/specs/mock/setup/SetupWithoutFesWhenFesReturnsError.spec.ts: -------------------------------------------------------------------------------- 1 | import { MockApi } from 'api-mocks/mock'; 2 | import { SetupKeyScreen, SplashScreen } from '../../../screenobjects/all-screens'; 3 | 4 | describe('SETUP: ', () => { 5 | it('enterprise app allows setup without FES when FES returns 404', async () => { 6 | const mockApi = new MockApi(); 7 | mockApi.fesConfig = { 8 | returnError: { 9 | code: 404, 10 | message: 'on error 404 app should act like there is no FES', 11 | }, 12 | }; 13 | await mockApi.withMockedApis(async () => { 14 | await SplashScreen.mockLogin(); 15 | await SetupKeyScreen.checkNoBackupsFoundScreen(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /appium/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["./**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /appium/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "transpileOnly": true, 4 | "require": ["tsconfig-paths/register"] 5 | }, 6 | "compilerOptions": { 7 | "outDir": "./.tsbuild/", 8 | "lib": ["ES2022", "DOM"], 9 | "module": "commonjs", 10 | "target": "ES2022", 11 | "types": [ 12 | "node", 13 | "jest", 14 | "@wdio/globals/types", 15 | "@wdio/jasmine-framework", 16 | "@wdio/junit-reporter", 17 | "@wdio/appium-service" 18 | ], 19 | "esModuleInterop": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "noImplicitReturns": true, 22 | "noUnusedLocals": true, 23 | "alwaysStrict": true, 24 | "noImplicitAny": true, 25 | "strictNullChecks": true, 26 | "noImplicitThis": true, 27 | "strictPropertyInitialization": true, 28 | "strictFunctionTypes": false, 29 | "sourceMap": false, 30 | "checkJs": false, 31 | "strict": true, 32 | "skipLibCheck": true, 33 | "baseUrl": "." 34 | }, 35 | "include": ["./api-mocks/**/*.ts", "./config/*.ts", "./tests/**/*.ts"] 36 | } 37 | -------------------------------------------------------------------------------- /code-design.md: -------------------------------------------------------------------------------- 1 | # Flowcrypt-ios Code Design 2 | 3 | ## Error Handling 4 | 5 | We have extension with `errorMessage` property for `Error`. 6 | 7 | It checks if `Error` has some custom message using `CustomStringConvertible` protocol, otherwise it uses `localizedDescription` property. 8 | ```swift 9 | public extension Error { 10 | var errorMessage: String { 11 | switch self { 12 | case let self as CustomStringConvertible: 13 | return String(describing: self) 14 | default: 15 | return localizedDescription 16 | } 17 | } 18 | } 19 | ``` 20 | 21 | Here is example of error with custom messages - https://github.com/FlowCrypt/flowcrypt-ios/blob/master/FlowCrypt/Functionality/Services/Compose%20Message%20Service/ComposeMessageError.swift 22 | 23 | ## Task Handling 24 | 25 | - There should generally be only one "grand" try/catch inside each `Task`. 26 | - There should be generally a single `Task` right in each handler. And then all errors downstream should mostly be re-thrown. 27 | -------------------------------------------------------------------------------- /docker-mailserver/.env: -------------------------------------------------------------------------------- 1 | 2 | # ----------------------------------------------------------------------------------------------------------------------------- 3 | # --------------------- General Settings -------------------------------------------------------------------------------------- 4 | # ----------------------------------------------------------------------------------------------------------------------------- 5 | 6 | HOSTNAME=mail 7 | DOMAINNAME=flowcrypt.test 8 | CONTAINER_NAME=mail 9 | 10 | -------------------------------------------------------------------------------- /docker-mailserver/check_email_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -uxo pipefail 4 | 5 | # this script tests the mailserver, make sure it accepts connections 6 | # make sure that your /etc/hosts includes line "217.163.30.119 mail" 7 | 8 | EMAILPORT=10025 9 | EMAILHOST=mail 10 | TMPFILE=/tmp/telnet-output.log 11 | 12 | ( sleep 1; echo -en "EHLO debug\r\n"; sleep 1; ) | telnet $EMAILHOST $EMAILPORT > $TMPFILE 13 | 14 | if ! grep -q 'mail.flowcrypt.test' $TMPFILE ; then 15 | echo "failed to confirm that SMTP server is running at '$EMAILHOST' port '$EMAILPORT':" 16 | cat $TMPFILE 17 | exit 1 18 | else 19 | echo "success - SMTP server at '$EMAILHOST' port '$EMAILPORT' is running" 20 | fi 21 | -------------------------------------------------------------------------------- /docker-mailserver/config/dovecot-quotas.cf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/docker-mailserver/config/dovecot-quotas.cf -------------------------------------------------------------------------------- /docker-mailserver/config/dovecot.cf: -------------------------------------------------------------------------------- 1 | # File for additional dovecot configurations. 2 | # For more informations read http://wiki.dovecot.org/BasicConfiguration 3 | 4 | mail_max_userip_connections = 100 5 | ssl = yes 6 | disable_plaintext_auth=no 7 | -------------------------------------------------------------------------------- /docker-mailserver/config/postfix-aliases.cf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowCrypt/flowcrypt-ios/8c1c745a32322fbef36d5ffc6b763b559cb7c5ea/docker-mailserver/config/postfix-aliases.cf -------------------------------------------------------------------------------- /docker-mailserver/config/ssl/mail.flowcrypt.test-req.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICuTCCAaECAQAwdDELMAkGA1UEBhMCQ1oxDjAMBgNVBAgMBURlYnVnMRIwEAYD 3 | VQQKDAlGbG93Q3J5cHQxHDAaBgNVBAMME21haWwuZmxvd2NyeXB0LnRlc3QxIzAh 4 | BgkqhkiG9w0BCQEWFGFkbWluQGZsb3djcnlwdC50ZXN0MIIBIjANBgkqhkiG9w0B 5 | AQEFAAOCAQ8AMIIBCgKCAQEA0fFcBa4fyMCMB3Q3q03ccarTX4qA2iuIoe8zL4rA 6 | N1g29aeR1KYNcQm92bHpZX8ZESRC2M5BW1+mRGay8lF85eD5bZLhD3oY1BfgIFnF 7 | USLnJ+PfyXGWeRbb3rY1pVMp1qtNhqNIPyyobHWN7M3di1zQsJ+wFB4A+w0kEx5u 8 | PqZoTk1p4y6jwm3AjpTapcH8McIUGxlzvWoHVxTu4hV1QTButP8RhSPl4NAwIhp6 9 | dIzjP5jVMCJTOJ1b2LzI7RYFCvjGzCKKh2g+1gES5GBjbzNPxxIyIOWGeBSBw6UE 10 | uwJezxWG0EHg4QsAElMCzJAM1KQbT8+4FVoLl5ypp7l8ewIDAQABoAAwDQYJKoZI 11 | hvcNAQELBQADggEBAHZAQuJMnY9jxHlSM9h7ygbm/ei7MURopzR/IbjZMFLWzQ2v 12 | Lni88/6V4c5vN5D+AchlaHeS7i0ddt4KgH1jqIBz3VofiJT8GAJENk5LqsqPfX5y 13 | pXDs3HuT4TMLdvk0qn8mkNK/YzXpyupYcckGDjWd+nWBACL9yA0NPbKEUfqAXKpx 14 | 9ILcGH7Jq/UQWW7ZRR8feLrrgt7kJCfGKdskEl/Kulmdw+19Kf868gqN+M5uZTUk 15 | OpI2R3SG1uuDT1TSEVhYGO6n1MZU/MLEfoH01M890LVNt1ZIadCPv3aLWCCJQf15 16 | ZQHLVNTPKGzxwT3o09JLMX77K1bTx4ovpWL5pGA= 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /docker-mailserver/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | mail: 4 | image: flowcrypt/flowcrypt-email-server:0.0.5 5 | hostname: ${HOSTNAME} 6 | domainname: ${DOMAINNAME} 7 | container_name: ${CONTAINER_NAME} 8 | ports: 9 | - "10025:25" 10 | - "10143:143" 11 | - "10587:587" 12 | - "10993:993" 13 | volumes: 14 | #uncomment this only to make a backup of mailboxes 15 | #- ./maildata_volume:/var/mail 16 | - ./config/:/tmp/docker-mailserver/ 17 | env_file: 18 | - .env 19 | - env-mailserver 20 | cap_add: 21 | - NET_ADMIN 22 | - SYS_PTRACE 23 | restart: "no" 24 | volumes: 25 | mailstate: 26 | driver: local 27 | maillogs: 28 | driver: local 29 | -------------------------------------------------------------------------------- /docker-mailserver/restart_email_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # re-run docker-mailserver 4 | 5 | ./stop_email_server.sh 6 | ./run_email_server.sh -------------------------------------------------------------------------------- /docker-mailserver/run_email_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | # run docker-mailserver 6 | 7 | docker-compose up -d mail -------------------------------------------------------------------------------- /docker-mailserver/stop_email_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # stop docker-mailserver 4 | 5 | docker compose down -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app 2 | # apple_id("[[APPLE_ID]]") # Your Apple email address 3 | 4 | 5 | # For more information about the Appfile, see: 6 | # https://docs.fastlane.tools/advanced/#appfile 7 | 8 | app_identifier "com.flowcrypt.as.ios.debug" # The bundle identifier of your app 9 | apple_id "tom@flowcrypt.com" # Your Apple email address 10 | team_id "6DZ6CC3YMY" # Developer Portal Team ID 11 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # fastlane/Fastfile 2 | ENV["FASTLANE_EXPLICIT_OPEN_SIMULATOR"] = "1" 3 | ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" 4 | ENV["FASTLANE_XCODE_LIST_TIMEOUT"] = "120" 5 | 6 | default_platform(:ios) 7 | 8 | platform :ios do 9 | 10 | desc "Build for UI tests" 11 | lane :build do 12 | scan( 13 | project: "FlowCrypt.xcodeproj", 14 | scheme: "Debug FlowCrypt", 15 | device: "iPhone 16 (18.2)", 16 | derived_data_path: "/var/tmp/derived_data/FlowCrypt", 17 | skip_detect_devices: true, 18 | build_for_testing: true, 19 | xcargs: "-skipPackagePluginValidation -skipMacroValidation", 20 | ) 21 | end 22 | 23 | desc "Run Swift tests" 24 | lane :test do 25 | scan( 26 | project: "FlowCrypt.xcodeproj", 27 | scheme: "FlowCryptAppTests", 28 | device: "iPhone 16 (18.2)", 29 | test_without_building: true, 30 | derived_data_path: "/var/tmp/derived_data/FlowCrypt", 31 | xcargs: "-skipPackagePluginValidation -skipMacroValidation", 32 | parallel_testing: false, 33 | disable_concurrent_testing: true 34 | ) 35 | end 36 | 37 | end 38 | 39 | -------------------------------------------------------------------------------- /fastlane/Snapfile: -------------------------------------------------------------------------------- 1 | # Uncomment the lines below you want to change by removing the # in the beginning 2 | 3 | devices([ 4 | # "iPhone 8", 5 | "iPhone 11", 6 | ]) 7 | 8 | # languages([ 9 | # "en-US", 10 | # "de-DE", 11 | # "it-IT", 12 | # ["pt", "pt_BR"] # Portuguese with Brazilian locale 13 | # ]) 14 | 15 | 16 | # Where should the resulting screenshots be stored? 17 | # output_directory("./screenshots") 18 | 19 | # remove the '#' to clear all previously generated screenshots before creating new ones 20 | clear_previous_screenshots(true) 21 | 22 | # Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments 23 | # launch_arguments(["-favColor red"]) 24 | 25 | # For more information about all available options run 26 | # fastlane action snapshot 27 | erase_simulator(true) 28 | reinstall_app(true) 29 | stop_after_first_error(true) 30 | disable_slide_to_type(true) 31 | 32 | -------------------------------------------------------------------------------- /flowcrypt-ios.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "appium" 5 | }, 6 | { 7 | "name": "core", 8 | "path": "Core" 9 | }, 10 | { 11 | "name": "ios", 12 | "path": "." 13 | } 14 | ], 15 | "settings": { 16 | "typescript.tsdk": "./node_modules/typescript/lib", 17 | "editor.defaultFormatter": "esbenp.prettier-vscode", 18 | "editor.formatOnSave": true, 19 | "editor.formatOnPaste": true, 20 | "editor.tabSize": 2, 21 | "editor.insertSpaces": true, 22 | "editor.detectIndentation": false, 23 | "editor.codeActionsOnSave": { 24 | "source.fixAll.eslint": "explicit" 25 | }, 26 | "errorLens.enabledDiagnosticLevels": ["error"], 27 | "eslint.validate": ["javascript", "typescript"], 28 | "typescript.surveys.enabled": false, 29 | "typescript.preferences.quoteStyle": "single", 30 | "editor.rulers": [160] 31 | }, 32 | "extensions": { 33 | "recommendations": [ 34 | "dbaeumer.vscode-eslint", 35 | "esbenp.prettier-vscode", 36 | "usernamehw.errorlens" 37 | ] 38 | } 39 | } 40 | --------------------------------------------------------------------------------