diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index d5cc25c4b..f76f43668 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -53,16 +53,16 @@ 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA9701B2D5052225A0414 /* SignInViewController.swift */; }; 50531BE42629B9A80039BAE9 /* AttachmentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50531BE32629B9A80039BAE9 /* AttachmentNode.swift */; }; 510A260126FDFEBE00163271 /* MailCore2 in Frameworks */ = {isa = PBXBuildFile; productRef = 510A260026FDFEBE00163271 /* MailCore2 */; }; + 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */ = {isa = PBXBuildFile; productRef = 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; + 51B4AE4F27137CB70001F33B /* Promises in Frameworks */ = {isa = PBXBuildFile; productRef = 51B4AE4E27137CB70001F33B /* Promises */; }; 51E13F12270F92BA00F287CA /* IDZSwiftCommonCrypto in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */; }; 51E13F15270F92F200F287CA /* BigInt in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F14270F92F200F287CA /* BigInt */; }; 51E13F18270F934C00F287CA /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 51E13F17270F934C00F287CA /* MBProgressHUD */; }; 51E1673D270DAFF900D27C52 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 51E1673C270DAFF900D27C52 /* Localizable.stringsdict */; }; 51E16746270F301F00D27C52 /* GoogleAPIClientForREST_Gmail in Frameworks */ = {isa = PBXBuildFile; productRef = 51E16745270F301F00D27C52 /* GoogleAPIClientForREST_Gmail */; }; 51E16749270F303100D27C52 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 51E16748270F303100D27C52 /* GoogleSignIn */; }; - 51E1674F270F341300D27C52 /* Promises in Frameworks */ = {isa = PBXBuildFile; productRef = 51E1674E270F341300D27C52 /* Promises */; }; - 51E16755270F356500D27C52 /* GTMAppAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 51E16754270F356500D27C52 /* GTMAppAuth */; }; 51E1675D270F36A400D27C52 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 51E1675C270F36A400D27C52 /* Realm */; }; 51E1675F270F36A400D27C52 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 51E1675E270F36A400D27C52 /* RealmSwift */; }; 5298EA408FEC36021F7558BD /* Pods_FlowCrypt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4753E9A27694B4D34C980FFA /* Pods_FlowCrypt.framework */; }; @@ -769,16 +769,16 @@ files = ( D2CDC3E224052D54002B045F /* FlowCryptCommon.framework in Frameworks */, D2CDC3DD24052D50002B045F /* FlowCryptUI.framework in Frameworks */, - 51E1674F270F341300D27C52 /* Promises in Frameworks */, 51E13F15270F92F200F287CA /* BigInt in Frameworks */, 51E16746270F301F00D27C52 /* GoogleAPIClientForREST_Gmail in Frameworks */, 510A260126FDFEBE00163271 /* MailCore2 in Frameworks */, - 51E16755270F356500D27C52 /* GTMAppAuth in Frameworks */, 51E1675D270F36A400D27C52 /* Realm in Frameworks */, 51E13F12270F92BA00F287CA /* IDZSwiftCommonCrypto in Frameworks */, 51E13F18270F934C00F287CA /* MBProgressHUD in Frameworks */, 5298EA408FEC36021F7558BD /* Pods_FlowCrypt.framework in Frameworks */, 51E1675F270F36A400D27C52 /* RealmSwift in Frameworks */, + 51B4AE4F27137CB70001F33B /* Promises in Frameworks */, + 512C1414271077F8002DE13F /* GoogleAPIClientForREST_PeopleService in Frameworks */, 51E16749270F303100D27C52 /* GoogleSignIn in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2066,13 +2066,13 @@ 510A260026FDFEBE00163271 /* MailCore2 */, 51E16745270F301F00D27C52 /* GoogleAPIClientForREST_Gmail */, 51E16748270F303100D27C52 /* GoogleSignIn */, - 51E1674E270F341300D27C52 /* Promises */, - 51E16754270F356500D27C52 /* GTMAppAuth */, 51E1675C270F36A400D27C52 /* Realm */, 51E1675E270F36A400D27C52 /* RealmSwift */, 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */, 51E13F14270F92F200F287CA /* BigInt */, 51E13F17270F934C00F287CA /* MBProgressHUD */, + 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */, + 51B4AE4E27137CB70001F33B /* Promises */, ); productName = FlowCrypt; productReference = C132B9B01EC2DBD800763715 /* FlowCrypt.app */; @@ -2183,12 +2183,11 @@ 510A25FF26FDFEBE00163271 /* XCRemoteSwiftPackageReference "mailcore2" */, 51E16744270F301F00D27C52 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */, 51E16747270F303100D27C52 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, - 51E1674D270F341200D27C52 /* XCRemoteSwiftPackageReference "promises" */, - 51E16753270F356500D27C52 /* XCRemoteSwiftPackageReference "GTMAppAuth" */, 51E1675B270F36A400D27C52 /* XCRemoteSwiftPackageReference "realm-cocoa" */, 51E13F10270F92BA00F287CA /* XCRemoteSwiftPackageReference "IDZSwiftCommonCrypto" */, 51E13F13270F92F100F287CA /* XCRemoteSwiftPackageReference "BigInt" */, 51E13F16270F934C00F287CA /* XCRemoteSwiftPackageReference "MBProgressHUD" */, + 51B4AE4D27137CB70001F33B /* XCRemoteSwiftPackageReference "promises" */, ); productRefGroup = C132B9B11EC2DBD800763715 /* Products */; projectDirPath = ""; @@ -3379,6 +3378,7 @@ ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -3631,7 +3631,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FlowCryptUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3960,6 +3959,14 @@ kind = branch; }; }; + 51B4AE4D27137CB70001F33B /* XCRemoteSwiftPackageReference "promises" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/google/promises"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; 51E13F10270F92BA00F287CA /* XCRemoteSwiftPackageReference "IDZSwiftCommonCrypto" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/iosdevzone/IDZSwiftCommonCrypto"; @@ -4000,22 +4007,6 @@ minimumVersion = 6.0.0; }; }; - 51E1674D270F341200D27C52 /* XCRemoteSwiftPackageReference "promises" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/google/promises"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; - }; - }; - 51E16753270F356500D27C52 /* XCRemoteSwiftPackageReference "GTMAppAuth" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/google/GTMAppAuth"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; 51E1675B270F36A400D27C52 /* XCRemoteSwiftPackageReference "realm-cocoa" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/realm-cocoa"; @@ -4032,6 +4023,16 @@ package = 510A25FF26FDFEBE00163271 /* XCRemoteSwiftPackageReference "mailcore2" */; productName = MailCore2; }; + 512C1413271077F8002DE13F /* GoogleAPIClientForREST_PeopleService */ = { + isa = XCSwiftPackageProductDependency; + package = 51E16744270F301F00D27C52 /* XCRemoteSwiftPackageReference "google-api-objectivec-client-for-rest" */; + productName = GoogleAPIClientForREST_PeopleService; + }; + 51B4AE4E27137CB70001F33B /* Promises */ = { + isa = XCSwiftPackageProductDependency; + package = 51B4AE4D27137CB70001F33B /* XCRemoteSwiftPackageReference "promises" */; + productName = Promises; + }; 51E13F11270F92BA00F287CA /* IDZSwiftCommonCrypto */ = { isa = XCSwiftPackageProductDependency; package = 51E13F10270F92BA00F287CA /* XCRemoteSwiftPackageReference "IDZSwiftCommonCrypto" */; @@ -4057,16 +4058,6 @@ package = 51E16747270F303100D27C52 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; productName = GoogleSignIn; }; - 51E1674E270F341300D27C52 /* Promises */ = { - isa = XCSwiftPackageProductDependency; - package = 51E1674D270F341200D27C52 /* XCRemoteSwiftPackageReference "promises" */; - productName = Promises; - }; - 51E16754270F356500D27C52 /* GTMAppAuth */ = { - isa = XCSwiftPackageProductDependency; - package = 51E16753270F356500D27C52 /* XCRemoteSwiftPackageReference "GTMAppAuth" */; - productName = GTMAppAuth; - }; 51E1675C270F36A400D27C52 /* Realm */ = { isa = XCSwiftPackageProductDependency; package = 51E1675B270F36A400D27C52 /* XCRemoteSwiftPackageReference "realm-cocoa" */; diff --git a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8d66041a3..44d4ba74b 100644 --- a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -48,7 +48,7 @@ }, { "package": "GTMAppAuth", - "repositoryURL": "https://github.com/google/GTMAppAuth", + "repositoryURL": "https://github.com/google/GTMAppAuth.git", "state": { "branch": null, "revision": "40f4103fb52109032c05599a0c39ad43edbdf80a", diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 3cc02014a..04d0412fb 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -37,7 +37,7 @@ final class ComposeViewController: TableNodeViewController { private let filesManager: FilesManagerType private let photosManager: PhotosManagerType - private let searchThrottler = Throttler(seconds: 1) + private let search = PassthroughSubject() private let cloudContactProvider: CloudContactsProvider private let userDefaults: UserDefaults @@ -96,10 +96,9 @@ final class ComposeViewController: TableNodeViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - - // temporary disable search contacts - https://github.com/FlowCrypt/flowcrypt-ios/issues/217 - // showScopeAlertIfNeeded() + showScopeAlertIfNeeded() cancellable.forEach { $0.cancel() } + setupSearch() } deinit { @@ -147,6 +146,24 @@ extension ComposeViewController { } } +// MARK: - Search +extension ComposeViewController { + private func setupSearch() { + search + .debounce(for: .milliseconds(400), scheduler: RunLoop.main) + .removeDuplicates() + .compactMap { [weak self] in + guard $0.isNotEmpty else { + self?.updateState(with: .main) + return nil + } + return $0 + } + .sink { [weak self] in self?.searchEmail(with: $0) } + .store(in: &cancellable) + } +} + // MARK: - Keyboard extension ComposeViewController { @@ -535,15 +552,13 @@ extension ComposeViewController { } private func handleEditingChanged(with text: String?) { -// temporary disable search contacts - https://github.com/FlowCrypt/flowcrypt-ios/issues/217 -// guard let text = text, text.isNotEmpty else { -// updateState(with: .main) -// return -// } -// -// searchThrottler.throttle { [weak self] in -// self?.searchEmail(with: text) -// } + guard let text = text, text.isNotEmpty else { + search.send("") + updateState(with: .main) + return + } + + search.send(text) } private func handleDidBeginEditing() { @@ -667,7 +682,13 @@ extension ComposeViewController { extension ComposeViewController { private func updateState(with newState: State) { state = newState - node.reloadSections(IndexSet(integer: 0), with: .fade) + + switch state { + case .main: + node.reloadSections([0, 1], with: .fade) + case .searchEmails: + node.reloadSections([1], with: .fade) + } } } @@ -797,8 +818,6 @@ extension ComposeViewController { } } -// temporary disable search contacts -// https://github.com/FlowCrypt/flowcrypt-ios/issues/217 extension ComposeViewController { private func showScopeAlertIfNeeded() { if shouldRenewToken(for: [.mail]), diff --git a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift index bab03ba07..f725c65c0 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift @@ -57,7 +57,7 @@ struct ComposeViewDecorator { InfoCellNode.Input( attributedText: email.attributed( .medium(17), - color: UIColor.black.withAlphaComponent(0.8), + color: UIColor.mainTextColor.withAlphaComponent(0.8), alignment: .left ), image: nil, diff --git a/FlowCrypt/Extensions/URLSessionExtension.swift b/FlowCrypt/Extensions/URLSessionExtension.swift index df0f1fbcf..6e909393c 100644 --- a/FlowCrypt/Extensions/URLSessionExtension.swift +++ b/FlowCrypt/Extensions/URLSessionExtension.swift @@ -27,7 +27,7 @@ extension URLSession { let res = response as? HTTPURLResponse let status = res?.statusCode ?? GeneralConstants.Global.generalError let urlMethod = urlRequest.httpMethod ?? "GET" - let urlString = urlRequest.url?.absoluteString ?? "??" + let urlString = urlRequest.url?.stringWithFilteredTokens ?? "??" let headers = urlRequest.headersWithFilteredTokens let message = "URLSession.call status:\(status) ms:\(trace.finish()) \(urlMethod) \(urlString), headers: \(headers)" Logger.nested("URLSession").logInfo(message) @@ -70,8 +70,8 @@ struct URLHeader { extension URLRequest { static func urlRequest( with urlString: String, - method: HTTPMetod, - body: Data?, + method: HTTPMetod = .get, + body: Data? = nil, headers: [URLHeader] = [] ) -> URLRequest { guard let url = URL(string: urlString) else { diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift index b3fd6fa0b..036c130be 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/CloudContactsProvider.swift @@ -6,64 +6,70 @@ // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // -import Foundation +import FlowCryptCommon import Promises +import GoogleAPIClientForREST_PeopleService protocol CloudContactsProvider { func searchContacts(query: String) -> Promise<[String]> } -final class UserContactsProvider { - private enum Constants { - static let scheme = "https" - static let host = "www.google.com" - static let searchPath = "/m8/feeds/contacts/default/thin" - } +enum CloudContactsProviderError: Error { + /// People API response parsing + case failedToParseData(Any?) + /// Provider Error + case providerError(Error) +} - private let dataService: DataService +final class UserContactsProvider { + private let logger = Logger.nested("UserContactsProvider") + private let userService: GoogleUserServiceType + private var peopleService: GTLRPeopleServiceService { + let service = GTLRPeopleServiceService() - private var token: String? { - dataService.token - } + if userService.authorization == nil { + logger.logWarning("authorization for current user is nil") + } - init(dataService: DataService = .shared) { - self.dataService = dataService + service.authorizer = userService.authorization + return service } - private func components(for path: String) -> URLComponents { - var components = URLComponents() - components.scheme = Constants.scheme - components.host = Constants.host - components.path = path - return components + init(userService: GoogleUserServiceType = GoogleUserService()) { + self.userService = userService + + // Warmup query for contacts cache + _ = self.searchContacts(query: "") } } extension UserContactsProvider: CloudContactsProvider { func searchContacts(query: String) -> Promise<[String]> { - guard let token = token else { - assertionFailure("token should not be nil") - return Promise(AppErr.unexpected("Missing token")) - } + let searchQuery = GTLRPeopleServiceQuery_PeopleSearchContacts.query() + searchQuery.readMask = "names,emailAddresses" + searchQuery.query = query - var searchComponents = components(for: Constants.searchPath) - searchComponents.queryItems = [ - URLQueryItem(name: "q", value: query), - URLQueryItem(name: "v", value: "3.0"), - URLQueryItem(name: "alt", value: "json"), - URLQueryItem(name: "access_token", value: token), - URLQueryItem(name: "start-index", value: "0") - ] + return Promise<[String]> { resolve, reject in + self.peopleService.executeQuery(searchQuery) { _, data, error in + if let error = error { + return reject(CloudContactsProviderError.providerError(error)) + } - guard let url = searchComponents.url else { - assertionFailure("Url should not be nil") - return Promise(AppErr.unexpected("Missing url")) - } + guard let response = data as? GTLRPeopleService_SearchResponse else { + return reject(AppErr.cast("GTLRPeopleService_SearchResponse")) + } + + guard let contacts = response.results else { + return reject(CloudContactsProviderError.failedToParseData(data)) + } + + let emails = contacts + .compactMap { $0.person?.emailAddresses } + .flatMap { $0 } + .compactMap { $0.value } - return Promise<[String]> { () -> [String] in - let response = try awaitPromise(URLSession.shared.call(URLRequest(url: url))) - let emails = try JSONDecoder().decode(GoogleContactsResponse.self, from: response.data).emails - return emails + resolve(emails) + } } } } diff --git a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/GoogleContactsResponse.swift b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/GoogleContactsResponse.swift index b4680294b..6b06cde3f 100644 --- a/FlowCrypt/Functionality/Mail Provider/Contacts Provider/GoogleContactsResponse.swift +++ b/FlowCrypt/Functionality/Mail Provider/Contacts Provider/GoogleContactsResponse.swift @@ -8,45 +8,28 @@ import Foundation -// swiftlint:disable nesting struct GoogleContactsResponse: Decodable { - let emails: [String] + let results: [PersonResult]? - private enum CodingKeys: String, CodingKey { - case feed + struct PersonResult: Decodable { + let person: Person - enum Entry: String, CodingKey { - case entry + struct Person: Decodable { + let emailAddresses: [EmailAddress] - enum AdressKeys: String, CodingKey { - case email = "gd$email" - - enum EmailKeys: String, CodingKey { - case address - } + struct EmailAddress: Decodable { + let value: String } } } +} - init(from decoder: Decoder) throws { - // top-level container - let container = try decoder.container(keyedBy: CodingKeys.self) - - do { - let feedContainer = try container.nestedContainer(keyedBy: CodingKeys.Entry.self, forKey: .feed) - - var entryContainer = try feedContainer.nestedUnkeyedContainer(forKey: .entry) - - var names = [String]() - while !entryContainer.isAtEnd { - let addressContainer = try entryContainer.nestedContainer(keyedBy: CodingKeys.Entry.AdressKeys.self) - var emailContainer = try addressContainer.nestedUnkeyedContainer(forKey: .email) - let resultContainer = try emailContainer.nestedContainer(keyedBy: CodingKeys.Entry.AdressKeys.EmailKeys.self) - names.append(try resultContainer.decode(String.self, forKey: .address)) - } - emails = names - } catch { - emails = [] - } +extension GoogleContactsResponse { + var emails: [String] { + results? + .map(\.person) + .flatMap(\.emailAddresses) + .map { String($0.value) } + ?? [] } } diff --git a/FlowCrypt/Functionality/Services/GeneralConstants.swift b/FlowCrypt/Functionality/Services/GeneralConstants.swift index 4da5daf99..cd078afb1 100644 --- a/FlowCrypt/Functionality/Services/GeneralConstants.swift +++ b/FlowCrypt/Functionality/Services/GeneralConstants.swift @@ -8,9 +8,7 @@ enum GeneralConstants { enum Gmail { static let clientID = "679326713487-8f07eqt1hvjvopgcjeie4dbtni4ig0rc.apps.googleusercontent.com" static let redirectURL = URL(string: "com.googleusercontent.apps.679326713487-8f07eqt1hvjvopgcjeie4dbtni4ig0rc:/oauthredirect")! - - // temporary disable search contacts - https://github.com/FlowCrypt/flowcrypt-ios/issues/217 - static let currentScope: [GoogleScope] = [.mail, .userInfo] + static let currentScope: [GoogleScope] = GoogleScope.allCases } enum Global { diff --git a/FlowCryptAppTests/GeneralConstantsTest.swift b/FlowCryptAppTests/GeneralConstantsTest.swift index 2a7c9b557..b0447cd59 100644 --- a/FlowCryptAppTests/GeneralConstantsTest.swift +++ b/FlowCryptAppTests/GeneralConstantsTest.swift @@ -28,10 +28,10 @@ class GeneralConstantsTest: XCTestCase { let currentScope: Set = Set(GeneralConstants.Gmail.currentScope.map { $0.value }) let expectedScope = Set([ "https://www.googleapis.com/auth/userinfo.profile", - "https://mail.google.com/" + "https://mail.google.com/", + "https://www.googleapis.com/auth/contacts.readonly" ]) XCTAssert(currentScope == expectedScope) - XCTAssertFalse(currentScope.contains("https://www.googleapis.com/auth/contacts.readonly")) // Client Id let clientId = GeneralConstants.Gmail.clientID diff --git a/FlowCryptCommon/Extensions/URLExtension.swift b/FlowCryptCommon/Extensions/URLExtension.swift index cf1c6af8f..ce77a834d 100644 --- a/FlowCryptCommon/Extensions/URLExtension.swift +++ b/FlowCryptCommon/Extensions/URLExtension.swift @@ -26,6 +26,18 @@ public extension URL { var mimeType: String { return Self.mimeTypes[pathExtension] ?? "text/plain" } + + var stringWithFilteredTokens: String { + guard var components = URLComponents(string: absoluteString), + let queryItems = components.queryItems + else { return absoluteString } + + components.queryItems = queryItems.map { + URLQueryItem(name: $0.name, value: $0.name.contains("token") ? "***" : $0.value) + } + + return components.url?.absoluteString ?? absoluteString + } } public extension String {