From 758eb04aa2dc152a0f97d7565df94c27ce5e87db Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 20 Jun 2022 21:12:41 -0700 Subject: [PATCH 1/9] update provisioning profile spec --- apple/Omnivore.xcodeproj/project.pbxproj | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apple/Omnivore.xcodeproj/project.pbxproj b/apple/Omnivore.xcodeproj/project.pbxproj index 473fe264d..4625c4ea4 100644 --- a/apple/Omnivore.xcodeproj/project.pbxproj +++ b/apple/Omnivore.xcodeproj/project.pbxproj @@ -1244,6 +1244,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -1274,6 +1275,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; @@ -1560,6 +1562,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.Omnivore-Extension"; PRODUCT_NAME = SafariExtension; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -1597,6 +1600,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.Omnivore-Extension"; PRODUCT_NAME = SafariExtension; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; @@ -1664,7 +1668,8 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = Entitlements/ShareExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = QJF2XZ86HB; INFOPLIST_FILE = InfoPlists/ShareExtension.plist; @@ -1677,6 +1682,7 @@ MARKETING_VERSION = 1.10.0; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension"; PRODUCT_NAME = ShareExtension; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_COMPILATION_MODE = wholemodule; @@ -1746,7 +1752,8 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = Entitlements/ShareExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = QJF2XZ86HB; INFOPLIST_FILE = InfoPlists/ShareExtension.plist; @@ -1759,6 +1766,7 @@ MARKETING_VERSION = 1.10.0; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension"; PRODUCT_NAME = ShareExtension; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; From f29597b53054d9933aafc5677e1e397ef96074b5 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 20 Jun 2022 21:38:08 -0700 Subject: [PATCH 2/9] add google sign in package --- .../xcshareddata/swiftpm/Package.resolved | 22 +++++++++++++++++-- apple/OmnivoreKit/Package.swift | 4 +++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apple/Omnivore.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apple/Omnivore.xcworkspace/xcshareddata/swiftpm/Package.resolved index fa6f4cbb3..7d1eeae0b 100644 --- a/apple/Omnivore.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apple/Omnivore.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/openid/AppAuth-iOS.git", "state" : { - "revision" : "01131d68346c8ae552961c768d583c715fbe1410", - "version" : "1.4.0" + "revision" : "33660c271c961f8ce1084cc13f2ea8195e864f7d", + "version" : "1.5.0" } }, { @@ -72,6 +72,15 @@ "version" : "9.1.2" } }, + { + "identity" : "googlesignin-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleSignIn-iOS", + "state" : { + "revision" : "9450e779619fc184d360c9f7ce61023587f7e1f4", + "version" : "6.2.2" + } + }, { "identity" : "googleutilities", "kind" : "remoteSourceControl", @@ -99,6 +108,15 @@ "version" : "1.7.0" } }, + { + "identity" : "gtmappauth", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GTMAppAuth.git", + "state" : { + "revision" : "b9d1683be336ba8c8d1c6867bafeb056a5399700", + "version" : "1.3.0" + } + }, { "identity" : "intercom-ios", "kind" : "remoteSourceControl", diff --git a/apple/OmnivoreKit/Package.swift b/apple/OmnivoreKit/Package.swift index 6fafc0cb0..cafef369a 100644 --- a/apple/OmnivoreKit/Package.swift +++ b/apple/OmnivoreKit/Package.swift @@ -32,6 +32,7 @@ let package = Package( .target( name: "Services", dependencies: [ + .product(name: "GoogleSignIn", package: "GoogleSignIn-iOS"), .product(name: "AppAuth", package: "AppAuth-iOS"), "Valet", .product(name: "SwiftGraphQL", package: "swift-graphql"), @@ -67,7 +68,8 @@ var dependencies: [Package.Dependency] { .package(url: "https://github.com/Square/Valet", from: "4.1.2"), .package(url: "https://github.com/maticzav/swift-graphql", from: "2.3.1"), .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"), - .package(url: "git@github.com:segmentio/analytics-swift.git", .upToNextMajor(from: "1.0.0")) + .package(url: "git@github.com:segmentio/analytics-swift.git", .upToNextMajor(from: "1.0.0")), + .package(url: "https://github.com/google/GoogleSignIn-iOS", from: "6.2.2") ] // #if canImport(UIKit) deps.append(.package(url: "https://github.com/PSPDFKit/PSPDFKit-SP", branch: "master")) From 321914dd80bf799af69042fd537c06a634ad63c5 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 20 Jun 2022 21:42:21 -0700 Subject: [PATCH 3/9] add google url scheme --- apple/InfoPlists/MacOmnivore.plist | 8 ++++++++ apple/InfoPlists/Omnivore.plist | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/apple/InfoPlists/MacOmnivore.plist b/apple/InfoPlists/MacOmnivore.plist index 7224e9b7f..d18829a1d 100644 --- a/apple/InfoPlists/MacOmnivore.plist +++ b/apple/InfoPlists/MacOmnivore.plist @@ -28,6 +28,14 @@ omnivore + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.267918240109-bdghlau7nsq2480c4l8gdgh6mrarokta + + CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/apple/InfoPlists/Omnivore.plist b/apple/InfoPlists/Omnivore.plist index 947099c90..c9a47cec4 100644 --- a/apple/InfoPlists/Omnivore.plist +++ b/apple/InfoPlists/Omnivore.plist @@ -32,6 +32,14 @@ omnivore + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.267918240109-bdghlau7nsq2480c4l8gdgh6mrarokta + + CFBundleVersion $(CURRENT_PROJECT_VERSION) From 77081290e57afca54de6518abd12abd4f3ad8145 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 20 Jun 2022 23:43:23 -0700 Subject: [PATCH 4/9] remove appauth and use google sign in lib instead --- apple/OmnivoreKit/Package.swift | 2 - .../Views/Registration/RegistrationView.swift | 27 +- .../Sources/App/Views/RootView/RootView.swift | 1 + .../Sources/App/Views/WelcomeView.swift | 4 +- .../Authentication/Authenticator.swift | 11 +- .../Services/Authentication/GoogleAuth.swift | 235 ++++++++++-------- 6 files changed, 149 insertions(+), 131 deletions(-) diff --git a/apple/OmnivoreKit/Package.swift b/apple/OmnivoreKit/Package.swift index cafef369a..f5ef78249 100644 --- a/apple/OmnivoreKit/Package.swift +++ b/apple/OmnivoreKit/Package.swift @@ -33,7 +33,6 @@ let package = Package( name: "Services", dependencies: [ .product(name: "GoogleSignIn", package: "GoogleSignIn-iOS"), - .product(name: "AppAuth", package: "AppAuth-iOS"), "Valet", .product(name: "SwiftGraphQL", package: "swift-graphql"), "Models", @@ -64,7 +63,6 @@ var appPackageDependencies: [Target.Dependency] { var dependencies: [Package.Dependency] { var deps: [Package.Dependency] = [ - .package(url: "https://github.com/openid/AppAuth-iOS.git", .upToNextMajor(from: "1.4.0")), .package(url: "https://github.com/Square/Valet", from: "4.1.2"), .package(url: "https://github.com/maticzav/swift-graphql", from: "2.3.1"), .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"), diff --git a/apple/OmnivoreKit/Sources/App/Views/Registration/RegistrationView.swift b/apple/OmnivoreKit/Sources/App/Views/Registration/RegistrationView.swift index f7d5a496c..5d71bf922 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Registration/RegistrationView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Registration/RegistrationView.swift @@ -66,21 +66,18 @@ final class RegistrationViewModel: ObservableObject { .store(in: &subscriptions) } - func handleGoogleAuth(authenticator: Authenticator) { - authenticator - .handleGoogleAuth(presentingViewController: presentingViewController()) - .sink( - receiveCompletion: { [weak self] completion in - guard case let .failure(loginError) = completion else { return } - self?.loginError = loginError - }, - receiveValue: { [weak self] isNewAccount in - if isNewAccount { - self?.registrationState = .createProfile(userProfile: UserProfile(username: "", name: "")) - } - } - ) - .store(in: &subscriptions) + func handleGoogleAuth(authenticator: Authenticator) async { + guard let presentingViewController = presentingViewController() else { return } + let googleAuthResponse = await authenticator.handleGoogleAuth(presenting: presentingViewController) + + switch googleAuthResponse { + case let .loginError(error): + loginError = error + case .newOmnivoreUser: + registrationState = .createProfile(userProfile: UserProfile(username: "", name: "")) + case .existingOmnivoreUser: + break + } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift index a946c7eb7..77f6c2982 100644 --- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift @@ -97,6 +97,7 @@ struct InnerRootView: View { } } #endif + .onOpenURL { Authenticator.handleGoogleURL(url: $0) } } #if os(iOS) diff --git a/apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift b/apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift index 4a45a70e3..32509e4e9 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift @@ -139,7 +139,9 @@ struct WelcomeView: View { if AppKeys.sharedInstance?.iosClientGoogleId != nil { GoogleAuthButton { - viewModel.handleGoogleAuth(authenticator: authenticator) + Task { + await viewModel.handleGoogleAuth(authenticator: authenticator) + } } } } diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift b/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift index a31c56ba3..354c834ad 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift @@ -1,6 +1,6 @@ -import AppAuth import Combine import Foundation +import GoogleSignIn import Models import Utils import WebKit @@ -8,6 +8,10 @@ import WebKit public final class Authenticator: ObservableObject { public static var unregisterIntercomUser: (() -> Void)? + public static func handleGoogleURL(url: URL) { + GIDSignIn.sharedInstance.handle(url) + } + public enum AuthStatus { case loggedOut case pendingUser @@ -20,13 +24,8 @@ public final class Authenticator: ObservableObject { let networker: Networker var subscriptions = Set() - var currentAuthorizationFlow: OIDExternalUserAgentSession? var pendingUserToken: String? - #if os(macOS) - var authRedirectHandler: OIDRedirectHTTPHandler? - #endif - public init(networker: Networker) { self.networker = networker self.isLoggedIn = ValetKey.authToken.exists diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift b/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift index be2429db2..b3eb47192 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift @@ -1,123 +1,144 @@ -import AppAuth import Combine import Foundation +import GoogleSignIn import Models import Utils -#if os(iOS) - import UIKit +public enum GoogleAuthResponse { + case loginError(error: LoginError) + case newOmnivoreUser + case existingOmnivoreUser +} - public extension Authenticator { - func handleGoogleAuth(presentingViewController: PlatformViewController?) -> AnyPublisher { - Future { [weak self] promise in - guard let self = self, let presenting = presentingViewController else { return } - - // swiftlint:disable:next line_length - self.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: self.googleAuthRequest(redirectURL: nil), presenting: presenting) { authState, authError in - self.resolveAuthResponse(promise: promise, authState: authState, authError: authError) - } - } - .eraseToAnyPublisher() - } +extension Authenticator { + public func handleGoogleAuth(presenting: PlatformViewController) async -> GoogleAuthResponse { + let authToken = try? await googleSignIn(presenting: presenting) + guard let authToken = authToken else { return .loginError(error: .unauthorized) } + // TODO: sync with server + return .existingOmnivoreUser } -#endif -#if os(macOS) - import AppKit - - public extension Authenticator { - func handleGoogleAuth(presentingViewController _: PlatformViewController?) -> AnyPublisher { - authRedirectHandler = OIDRedirectHTTPHandler( - successURL: URL(string: "https://omnivore.app")! - ) - let redirectURL = authRedirectHandler?.startHTTPListener(nil) - let authRequest = googleAuthRequest(redirectURL: redirectURL) - - return Future { [weak self] promise in - guard let self = self else { return } - - // swiftlint:disable:next line_length - self.authRedirectHandler?.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: authRequest) { authState, authError in - NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) - self.resolveAuthResponse(promise: promise, authState: authState, authError: authError) + func googleSignIn(presenting: PlatformViewController) async throws -> String { + try await withCheckedThrowingContinuation { continuation in + let clientID = "\(AppKeys.sharedInstance?.iosClientGoogleId ?? "").apps.googleusercontent.com" + GIDSignIn.sharedInstance.signIn( + with: GIDConfiguration(clientID: clientID), + presenting: presenting + ) { user, error in + guard let user = user, error == nil else { + continuation.resume(throwing: LoginError.unauthorized) + return } - } - .eraseToAnyPublisher() - } - } -#endif -private extension Authenticator { - func resolveAuthResponse( - promise: @escaping (Result) -> Void, - authState: OIDAuthState?, - authError: Error? - ) { - if let idToken = authState?.lastTokenResponse?.idToken { - Task { - do { - let authPayload = try await networker.submitGoogleToken(idToken: idToken) - try ValetKey.authCookieString.setValue(authPayload.commentedAuthCookieString) - try ValetKey.authToken.setValue(authPayload.authToken) - DispatchQueue.main.async { - self.isLoggedIn = true - } - } catch { - if let error = error as? LoginError { - switch error { - case .unauthorized, .unknown: - self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) - case .network: - promise(.failure(error)) - } - self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) + user.authentication.do { authentication, error in + guard let idToken = authentication?.idToken, error == nil else { + continuation.resume(throwing: LoginError.unauthorized) + return } + + continuation.resume(returning: idToken) } } - } else { - resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) } } - - func resolveAuthResponseForAccountCreation( - promise: @escaping (Result) -> Void, - authState: OIDAuthState?, - authError _: Error? - ) { - if let idToken = authState?.lastTokenResponse?.idToken { - let params = CreatePendingAccountParams(token: idToken, provider: .google, fullName: nil) - let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() - - networker - .createPendingUser(params: encodedParams) - .sink { completion in - guard case let .failure(serverError) = completion else { return } - promise(.failure(LoginError.make(serverError: serverError))) - } receiveValue: { [weak self] in - self?.pendingUserToken = $0.pendingUserToken - promise(.success(true)) - } - .store(in: &subscriptions) - } else { - promise(.failure(.unauthorized)) - } - } - - func googleAuthRequest(redirectURL: URL?) -> OIDAuthorizationRequest { - let authEndpoint = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")! - let tokenEndpoint = URL(string: "https://www.googleapis.com/oauth2/v4/token")! - let iosClientGoogleId = AppKeys.sharedInstance?.iosClientGoogleId ?? "" - let scopes = ["profile", "email"] - - return OIDAuthorizationRequest( - configuration: OIDServiceConfiguration(authorizationEndpoint: authEndpoint, tokenEndpoint: tokenEndpoint), - clientId: "\(iosClientGoogleId).apps.googleusercontent.com", - scopes: scopes, - redirectURL: redirectURL ?? URL( - string: "com.googleusercontent.apps.\(iosClientGoogleId):/oauth2redirect/google" - )!, - responseType: "code", - additionalParameters: nil - ) - } } + +// #if os(iOS) +// import UIKit +// +// public extension Authenticator { +// func handleGoogleAuthDep(presentingViewController: PlatformViewController?) -> AnyPublisher { +// Future { [weak self] promise in +// guard let self = self, let presenting = presentingViewController else { return } +// +// // swiftlint:disable:next line_length +// self.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: self.googleAuthRequest(redirectURL: nil), presenting: presenting) { authState, authError in +// self.resolveAuthResponse(promise: promise, authState: authState, authError: authError) +// } +// } +// .eraseToAnyPublisher() +// } +// } +// #endif + +// #if os(macOS) +// import AppKit +// +// public extension Authenticator { +// func handleGoogleAuthDep(presentingViewController _: PlatformViewController?) -> AnyPublisher { +// authRedirectHandler = OIDRedirectHTTPHandler( +// successURL: URL(string: "https://omnivore.app")! +// ) +// let redirectURL = authRedirectHandler?.startHTTPListener(nil) +// let authRequest = googleAuthRequest(redirectURL: redirectURL) +// +// return Future { [weak self] promise in +// guard let self = self else { return } +// +// // swiftlint:disable:next line_length +// self.authRedirectHandler?.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: authRequest) { authState, authError in +// NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) +// self.resolveAuthResponse(promise: promise, authState: authState, authError: authError) +// } +// } +// .eraseToAnyPublisher() +// } +// } +// #endif + +// private extension Authenticator { +// func resolveAuthResponse( +// promise: @escaping (Result) -> Void, +// authState: OIDAuthState?, +// authError: Error? +// ) { +// if let idToken = authState?.lastTokenResponse?.idToken { +// Task { +// do { +// let authPayload = try await networker.submitGoogleToken(idToken: idToken) +// try ValetKey.authCookieString.setValue(authPayload.commentedAuthCookieString) +// try ValetKey.authToken.setValue(authPayload.authToken) +// DispatchQueue.main.async { +// self.isLoggedIn = true +// } +// } catch { +// if let error = error as? LoginError { +// switch error { +// case .unauthorized, .unknown: +// self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) +// case .network: +// promise(.failure(error)) +// } +// self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) +// } +// } +// } +// } else { +// resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) +// } +// } +// +// func resolveAuthResponseForAccountCreation( +// promise: @escaping (Result) -> Void, +// authState: OIDAuthState?, +// authError _: Error? +// ) { +// if let idToken = authState?.lastTokenResponse?.idToken { +// let params = CreatePendingAccountParams(token: idToken, provider: .google, fullName: nil) +// let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() +// +// networker +// .createPendingUser(params: encodedParams) +// .sink { completion in +// guard case let .failure(serverError) = completion else { return } +// promise(.failure(LoginError.make(serverError: serverError))) +// } receiveValue: { [weak self] in +// self?.pendingUserToken = $0.pendingUserToken +// promise(.success(true)) +// } +// .store(in: &subscriptions) +// } else { +// promise(.failure(.unauthorized)) +// } +// } +// } From 30806e4c1be96142b6822ba80f7316dc985b2500 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 21 Jun 2022 13:38:49 -0700 Subject: [PATCH 5/9] send google token to server to exchange for omnivore token --- .../Authentication/AccountCreator.swift | 2 +- .../Services/Authentication/GoogleAuth.swift | 139 +++++------------- .../CreatePendingUserResource.swift | 23 ++- 3 files changed, 57 insertions(+), 107 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/AccountCreator.swift b/apple/OmnivoreKit/Sources/Services/Authentication/AccountCreator.swift index 468cfa4d2..3fbef9dc8 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/AccountCreator.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/AccountCreator.swift @@ -40,7 +40,7 @@ extension Authenticator { let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() return networker - .createPendingUser(params: encodedParams) + .createPendingUserDep(params: encodedParams) .tryMap { [weak self] in self?.pendingUserToken = $0.pendingUserToken return $0.pendingUserProfile diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift b/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift index b3eb47192..1302aae7a 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift @@ -1,4 +1,3 @@ -import Combine import Foundation import GoogleSignIn import Models @@ -12,10 +11,40 @@ public enum GoogleAuthResponse { extension Authenticator { public func handleGoogleAuth(presenting: PlatformViewController) async -> GoogleAuthResponse { - let authToken = try? await googleSignIn(presenting: presenting) - guard let authToken = authToken else { return .loginError(error: .unauthorized) } - // TODO: sync with server - return .existingOmnivoreUser + let idToken = try? await googleSignIn(presenting: presenting) + guard let idToken = idToken else { return .loginError(error: .unauthorized) } + + do { + let authPayload = try await networker.submitGoogleToken(idToken: idToken) + try ValetKey.authCookieString.setValue(authPayload.commentedAuthCookieString) + try ValetKey.authToken.setValue(authPayload.authToken) + DispatchQueue.main.async { + self.isLoggedIn = true + } + return .existingOmnivoreUser + } catch { + let loginError = (error as? LoginError) ?? .unknown + + switch loginError { + case .unauthorized, .unknown: + return await createPendingUser(idToken: idToken) + case .network: + return .loginError(error: .network) + } + } + } + + func createPendingUser(idToken: String) async -> GoogleAuthResponse { + do { + let params = CreatePendingAccountParams(token: idToken, provider: .google, fullName: nil) + let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() + let pendingUserAuthPayload = try await networker.createPendingUser(params: encodedParams) + pendingUserToken = pendingUserAuthPayload.pendingUserToken + return .newOmnivoreUser + } catch { + let loginError = LoginError.make(serverError: (error as? ServerError) ?? .unknown) + return .loginError(error: loginError) + } } func googleSignIn(presenting: PlatformViewController) async throws -> String { @@ -42,103 +71,3 @@ extension Authenticator { } } } - -// #if os(iOS) -// import UIKit -// -// public extension Authenticator { -// func handleGoogleAuthDep(presentingViewController: PlatformViewController?) -> AnyPublisher { -// Future { [weak self] promise in -// guard let self = self, let presenting = presentingViewController else { return } -// -// // swiftlint:disable:next line_length -// self.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: self.googleAuthRequest(redirectURL: nil), presenting: presenting) { authState, authError in -// self.resolveAuthResponse(promise: promise, authState: authState, authError: authError) -// } -// } -// .eraseToAnyPublisher() -// } -// } -// #endif - -// #if os(macOS) -// import AppKit -// -// public extension Authenticator { -// func handleGoogleAuthDep(presentingViewController _: PlatformViewController?) -> AnyPublisher { -// authRedirectHandler = OIDRedirectHTTPHandler( -// successURL: URL(string: "https://omnivore.app")! -// ) -// let redirectURL = authRedirectHandler?.startHTTPListener(nil) -// let authRequest = googleAuthRequest(redirectURL: redirectURL) -// -// return Future { [weak self] promise in -// guard let self = self else { return } -// -// // swiftlint:disable:next line_length -// self.authRedirectHandler?.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: authRequest) { authState, authError in -// NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) -// self.resolveAuthResponse(promise: promise, authState: authState, authError: authError) -// } -// } -// .eraseToAnyPublisher() -// } -// } -// #endif - -// private extension Authenticator { -// func resolveAuthResponse( -// promise: @escaping (Result) -> Void, -// authState: OIDAuthState?, -// authError: Error? -// ) { -// if let idToken = authState?.lastTokenResponse?.idToken { -// Task { -// do { -// let authPayload = try await networker.submitGoogleToken(idToken: idToken) -// try ValetKey.authCookieString.setValue(authPayload.commentedAuthCookieString) -// try ValetKey.authToken.setValue(authPayload.authToken) -// DispatchQueue.main.async { -// self.isLoggedIn = true -// } -// } catch { -// if let error = error as? LoginError { -// switch error { -// case .unauthorized, .unknown: -// self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) -// case .network: -// promise(.failure(error)) -// } -// self.resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) -// } -// } -// } -// } else { -// resolveAuthResponseForAccountCreation(promise: promise, authState: authState, authError: authError) -// } -// } -// -// func resolveAuthResponseForAccountCreation( -// promise: @escaping (Result) -> Void, -// authState: OIDAuthState?, -// authError _: Error? -// ) { -// if let idToken = authState?.lastTokenResponse?.idToken { -// let params = CreatePendingAccountParams(token: idToken, provider: .google, fullName: nil) -// let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() -// -// networker -// .createPendingUser(params: encodedParams) -// .sink { completion in -// guard case let .failure(serverError) = completion else { return } -// promise(.failure(LoginError.make(serverError: serverError))) -// } receiveValue: { [weak self] in -// self?.pendingUserToken = $0.pendingUserToken -// promise(.success(true)) -// } -// .store(in: &subscriptions) -// } else { -// promise(.failure(.unauthorized)) -// } -// } -// } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift index 069b87de4..f220dc39d 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift @@ -3,7 +3,7 @@ import Foundation import Models extension Networker { - func createPendingUser(params: Data) -> AnyPublisher { + func createPendingUserDep(params: Data) -> AnyPublisher { let urlRequest = URLRequest.create( baseURL: appEnvironment.serverBaseURL, urlPath: "/api/mobile-auth/sign-up", @@ -21,3 +21,24 @@ extension Networker { .eraseToAnyPublisher() } } + +extension Networker { + func createPendingUser(params: Data) async throws -> PendingUserAuthPayload { + let urlRequest = URLRequest.create( + baseURL: appEnvironment.serverBaseURL, + urlPath: "/api/mobile-auth/sign-up", + requestMethod: .post(params: params) + ) + + let resource = ServerResource( + urlRequest: urlRequest, + decode: PendingUserAuthPayload.decode + ) + + do { + return try await urlSession.performReq(resource: resource) + } catch { + throw (error as? ServerError) ?? .unknown + } + } +} From f449f52455fcaa7f090f5d0bdb372226e4824a2c Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 21 Jun 2022 14:38:52 -0700 Subject: [PATCH 6/9] convert more combine publishers to async --- .../Registration/CreateProfileView.swift | 24 +++---- .../Registration/NewAppleSignupView.swift | 26 ++++---- .../Views/Registration/RegistrationView.swift | 62 ++++++++----------- .../Authentication/AccountCreator.swift | 52 +++++++--------- .../Services/Authentication/AppleAuth.swift | 23 ++++--- .../Authentication/Authenticator.swift | 3 - .../CreateAccountResource.swift | 12 ++-- .../CreatePendingUserResource.swift | 21 ------- .../VerifyAppleTokenResource.swift | 26 -------- ...en.swift => VerifyAuthProviderToken.swift} | 9 +++ 10 files changed, 101 insertions(+), 157 deletions(-) delete mode 100644 apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAppleTokenResource.swift rename apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/{CreateGoogleToken.swift => VerifyAuthProviderToken.swift} (71%) diff --git a/apple/OmnivoreKit/Sources/App/Views/Registration/CreateProfileView.swift b/apple/OmnivoreKit/Sources/App/Views/Registration/CreateProfileView.swift index e218f96b2..db73ce795 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Registration/CreateProfileView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Registration/CreateProfileView.swift @@ -5,7 +5,7 @@ import SwiftUI import Utils import Views -final class CreateProfileViewModel: ObservableObject { +@MainActor final class CreateProfileViewModel: ObservableObject { private(set) var initialUserProfile = UserProfile(username: "", name: "", bio: nil) var isConfigured = false @@ -39,7 +39,9 @@ final class CreateProfileViewModel: ObservableObject { switch profileOrError { case let .left(userProfile): - submitProfile(userProfile: userProfile, authenticator: authenticator) + Task { + await submitProfile(userProfile: userProfile, authenticator: authenticator) + } case let .right(errorMessage): validationErrorMessage = errorMessage } @@ -76,16 +78,14 @@ final class CreateProfileViewModel: ObservableObject { .store(in: &subscriptions) } - func submitProfile(userProfile: UserProfile, authenticator: Authenticator) { - authenticator - .createAccount(userProfile: userProfile).sink( - receiveCompletion: { [weak self] completion in - guard case let .failure(loginError) = completion else { return } - self?.loginError = loginError - }, - receiveValue: { _ in } - ) - .store(in: &subscriptions) + func submitProfile(userProfile: UserProfile, authenticator: Authenticator) async { + do { + try await authenticator.createAccount(userProfile: userProfile) + } catch { + if let error = error as? LoginError { + loginError = error + } + } } func configure(profile: UserProfile, dataService: DataService) { diff --git a/apple/OmnivoreKit/Sources/App/Views/Registration/NewAppleSignupView.swift b/apple/OmnivoreKit/Sources/App/Views/Registration/NewAppleSignupView.swift index d24298116..15f333b90 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Registration/NewAppleSignupView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Registration/NewAppleSignupView.swift @@ -5,23 +5,21 @@ import SwiftUI import Utils import Views -final class NewAppleSignupViewModel: ObservableObject { +@MainActor final class NewAppleSignupViewModel: ObservableObject { @Published var loginError: LoginError? var subscriptions = Set() init() {} - func submitProfile(userProfile: UserProfile, authenticator: Authenticator) { - authenticator - .createAccount(userProfile: userProfile).sink( - receiveCompletion: { [weak self] completion in - guard case let .failure(loginError) = completion else { return } - self?.loginError = loginError - }, - receiveValue: { _ in } - ) - .store(in: &subscriptions) + func submitProfile(userProfile: UserProfile, authenticator: Authenticator) async { + do { + try await authenticator.createAccount(userProfile: userProfile) + } catch { + if let error = error as? LoginError { + loginError = error + } + } } } @@ -48,7 +46,11 @@ struct NewAppleSignupView: View { VStack { Button( - action: { viewModel.submitProfile(userProfile: userProfile, authenticator: authenticator) }, + action: { + Task { + await viewModel.submitProfile(userProfile: userProfile, authenticator: authenticator) + } + }, label: { Text("Continue") } ) .buttonStyle(SolidCapsuleButtonStyle(color: .appDeepBackground, width: 300)) diff --git a/apple/OmnivoreKit/Sources/App/Views/Registration/RegistrationView.swift b/apple/OmnivoreKit/Sources/App/Views/Registration/RegistrationView.swift index 5d71bf922..863ac8751 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Registration/RegistrationView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Registration/RegistrationView.swift @@ -1,12 +1,11 @@ import AuthenticationServices -import Combine import Models import Services import SwiftUI import Utils import Views -final class RegistrationViewModel: ObservableObject { +@MainActor final class RegistrationViewModel: ObservableObject { enum RegistrationState { case createProfile(userProfile: UserProfile) case newAppleSignUp(userProfile: UserProfile) @@ -15,12 +14,10 @@ final class RegistrationViewModel: ObservableObject { @Published var loginError: LoginError? @Published var registrationState: RegistrationState? - var subscriptions = Set() - func handleAppleSignInCompletion(result: Result, authenticator: Authenticator) { switch AppleSigninPayload.parse(authResult: result) { case let .success(payload): - handleAppleToken(payload: payload, authenticator: authenticator) + Task { await handleAppleToken(payload: payload, authenticator: authenticator) } case let .failure(error): switch error { case .unauthorized, .unknown: @@ -31,39 +28,34 @@ final class RegistrationViewModel: ObservableObject { } } - private func handleAppleToken(payload: AppleSigninPayload, authenticator: Authenticator) { - authenticator.submitAppleToken(token: payload.token).sink( - receiveCompletion: { [weak self] completion in - guard case let .failure(loginError) = completion else { return } - switch loginError { - case .unauthorized, .unknown: - self?.handleAppleSignUp(authenticator: authenticator, payload: payload) - case .network: - self?.loginError = loginError - } - }, - receiveValue: { _ in } - ) - .store(in: &subscriptions) + private func handleAppleToken(payload: AppleSigninPayload, authenticator: Authenticator) async { + do { + try await authenticator.submitAppleToken(token: payload.token) + } catch { + let submitTokenError = (error as? LoginError) ?? .unknown + switch submitTokenError { + case .unauthorized, .unknown: + await handleAppleSignUp(authenticator: authenticator, payload: payload) + case .network: + loginError = submitTokenError + } + } } - private func handleAppleSignUp(authenticator: Authenticator, payload: AppleSigninPayload) { - authenticator - .createPendingAccountUsingApple(token: payload.token, name: payload.fullName) - .sink( - receiveCompletion: { [weak self] completion in - guard case let .failure(loginError) = completion else { return } - self?.loginError = loginError - }, - receiveValue: { [weak self] userProfile in - if userProfile.name.isEmpty { - self?.registrationState = .createProfile(userProfile: userProfile) - } else { - self?.registrationState = .newAppleSignUp(userProfile: userProfile) - } - } + private func handleAppleSignUp(authenticator: Authenticator, payload: AppleSigninPayload) async { + do { + let pendingUserProfile = try await authenticator.createPendingAccountUsingApple( + token: payload.token, + name: payload.fullName ) - .store(in: &subscriptions) + if pendingUserProfile.name.isEmpty { + registrationState = .createProfile(userProfile: pendingUserProfile) + } else { + registrationState = .newAppleSignUp(userProfile: pendingUserProfile) + } + } catch { + loginError = (error as? LoginError) ?? .unknown + } } func handleGoogleAuth(authenticator: Authenticator) async { diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/AccountCreator.swift b/apple/OmnivoreKit/Sources/Services/Authentication/AccountCreator.swift index 3fbef9dc8..a0e044364 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/AccountCreator.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/AccountCreator.swift @@ -1,4 +1,3 @@ -import Combine import Foundation import Models @@ -6,12 +5,12 @@ public extension Authenticator { func createPendingAccountUsingApple( token: String, name: PersonNameComponents? - ) -> AnyPublisher { + ) async throws -> UserProfile { let params = CreatePendingAccountParams(token: token, provider: .apple, fullName: name) - return createPendingAccount(params: params) + return try await createPendingAccount(params: params) } - func createAccount(userProfile: UserProfile) -> AnyPublisher { + func createAccount(userProfile: UserProfile) async throws { let params = CreateAccountParams( pendingUserToken: pendingUserToken ?? "", userProfile: userProfile @@ -19,36 +18,29 @@ public extension Authenticator { let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() - return networker - .createAccount(params: encodedParams) - .tryMap { [weak self] in - try ValetKey.authCookieString.setValue($0.commentedAuthCookieString) - try ValetKey.authToken.setValue($0.authToken) - self?.pendingUserToken = nil - self?.isLoggedIn = true + do { + let authPayload = try await networker.createAccount(params: encodedParams) + try ValetKey.authCookieString.setValue(authPayload.commentedAuthCookieString) + try ValetKey.authToken.setValue(authPayload.authToken) + DispatchQueue.main.async { + self.pendingUserToken = nil + self.isLoggedIn = true } - .mapError { error in - let serverError = (error as? ServerError) ?? ServerError.unknown - return LoginError.make(serverError: serverError) - } - .eraseToAnyPublisher() + } catch { + let serverError = (error as? ServerError) ?? ServerError.unknown + throw LoginError.make(serverError: serverError) + } } } extension Authenticator { - func createPendingAccount(params: CreatePendingAccountParams) -> AnyPublisher { - let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() - - return networker - .createPendingUserDep(params: encodedParams) - .tryMap { [weak self] in - self?.pendingUserToken = $0.pendingUserToken - return $0.pendingUserProfile - } - .mapError { error in - let serverError = (error as? ServerError) ?? ServerError.unknown - return LoginError.make(serverError: serverError) - } - .eraseToAnyPublisher() + func createPendingAccount(params: CreatePendingAccountParams) async throws -> UserProfile { + do { + let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() + let pendingUserAuthPayload = try await networker.createPendingUser(params: encodedParams) + return pendingUserAuthPayload.pendingUserProfile + } catch { + throw LoginError.make(serverError: (error as? ServerError) ?? .unknown) + } } } diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/AppleAuth.swift b/apple/OmnivoreKit/Sources/Services/Authentication/AppleAuth.swift index 22475dc5e..ae25129e9 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/AppleAuth.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/AppleAuth.swift @@ -3,18 +3,17 @@ import Foundation import Models public extension Authenticator { - func submitAppleToken(token: String) -> AnyPublisher { - networker - .submitAppleToken(token: token) - .tryMap { [weak self] in - try ValetKey.authCookieString.setValue($0.commentedAuthCookieString) - try ValetKey.authToken.setValue($0.authToken) - self?.isLoggedIn = true + func submitAppleToken(token: String) async throws { + do { + let authPayload = try await networker.submitAppleToken(token: token) + try ValetKey.authCookieString.setValue(authPayload.commentedAuthCookieString) + try ValetKey.authToken.setValue(authPayload.authToken) + DispatchQueue.main.async { + self.isLoggedIn = true } - .mapError { error in - let serverError = (error as? ServerError) ?? ServerError.unknown - return LoginError.make(serverError: serverError) - } - .eraseToAnyPublisher() + } catch { + let serverError = (error as? ServerError) ?? ServerError.unknown + throw LoginError.make(serverError: serverError) + } } } diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift b/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift index 354c834ad..cd2f0c66b 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift @@ -1,4 +1,3 @@ -import Combine import Foundation import GoogleSignIn import Models @@ -19,11 +18,9 @@ public final class Authenticator: ObservableObject { } @Published public internal(set) var isLoggedIn: Bool - @Published public var pendinguserProfile = UserProfile(username: "", name: "", bio: nil) let networker: Networker - var subscriptions = Set() var pendingUserToken: String? public init(networker: Networker) { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateAccountResource.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateAccountResource.swift index 3743c1875..5e0b6f196 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateAccountResource.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateAccountResource.swift @@ -1,8 +1,7 @@ -import Combine import Foundation extension Networker { - func createAccount(params: Data) -> AnyPublisher { + func createAccount(params: Data) async throws -> AuthPayload { let urlRequest = URLRequest.create( baseURL: appEnvironment.serverBaseURL, urlPath: "/api/mobile-auth/create-account", @@ -14,9 +13,10 @@ extension Networker { decode: AuthPayload.decode ) - return urlSession - .performRequest(resource: resource) - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() + do { + return try await urlSession.performReq(resource: resource) + } catch { + throw (error as? ServerError) ?? .unknown + } } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift index f220dc39d..596bca4ca 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift @@ -1,27 +1,6 @@ -import Combine import Foundation import Models -extension Networker { - func createPendingUserDep(params: Data) -> AnyPublisher { - let urlRequest = URLRequest.create( - baseURL: appEnvironment.serverBaseURL, - urlPath: "/api/mobile-auth/sign-up", - requestMethod: .post(params: params) - ) - - let resource = ServerResource( - urlRequest: urlRequest, - decode: PendingUserAuthPayload.decode - ) - - return urlSession - .performRequest(resource: resource) - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } -} - extension Networker { func createPendingUser(params: Data) async throws -> PendingUserAuthPayload { let urlRequest = URLRequest.create( diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAppleTokenResource.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAppleTokenResource.swift deleted file mode 100644 index e8a681d52..000000000 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAppleTokenResource.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Combine -import Foundation -import Models - -extension Networker { - func submitAppleToken(token: String) -> AnyPublisher { - let params = SignInParams(token: token, provider: .apple) - let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() - - let urlRequest = URLRequest.create( - baseURL: appEnvironment.serverBaseURL, - urlPath: "/api/mobile-auth/sign-in", - requestMethod: .post(params: encodedParams) - ) - - let resource = ServerResource( - urlRequest: urlRequest, - decode: AuthPayload.decode - ) - - return urlSession - .performRequest(resource: resource) - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } -} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateGoogleToken.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAuthProviderToken.swift similarity index 71% rename from apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateGoogleToken.swift rename to apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAuthProviderToken.swift index df241e3a7..52304dfbf 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateGoogleToken.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAuthProviderToken.swift @@ -2,8 +2,17 @@ import Foundation import Models extension Networker { + func submitAppleToken(token: String) async throws -> AuthPayload { + let params = SignInParams(token: token, provider: .apple) + return try await submitSignInParams(params: params) + } + func submitGoogleToken(idToken: String) async throws -> AuthPayload { let params = SignInParams(token: idToken, provider: .google) + return try await submitSignInParams(params: params) + } + + func submitSignInParams(params: SignInParams) async throws -> AuthPayload { let encodedParams = (try? JSONEncoder().encode(params)) ?? Data() let urlRequest = URLRequest.create( From 587cbe2f26886d1bfaa998f530b26202ec01742d Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 21 Jun 2022 14:51:26 -0700 Subject: [PATCH 7/9] remove unused Combine imports --- .../Sources/App/AppExtensions/Share/ShareExtensionScene.swift | 1 - apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift | 3 --- .../Sources/App/Views/Registration/NewAppleSignupView.swift | 3 --- apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift | 1 - .../Sources/Services/Authentication/AppleAuth.swift | 1 - .../OmnivoreKit/Sources/Services/DataService/DataService.swift | 2 -- .../Sources/Services/DataService/Mutations/SavePDF.swift | 1 - 7 files changed, 12 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift index 1b3225cfe..1d262edb7 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionScene.swift @@ -1,4 +1,3 @@ -import Combine import Foundation import Models import Services diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index 7d8fc2a40..cde19d366 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -1,4 +1,3 @@ -import Combine import CoreData import Models import Services @@ -11,8 +10,6 @@ import Views let item: LinkedItem? @Published var webAppWrapperViewModel: WebAppWrapperViewModel? - var subscriptions = Set() - init(linkedItemObjectID: NSManagedObjectID, dataService: DataService) { if let linkedItem = dataService.viewContext.object(with: linkedItemObjectID) as? LinkedItem { self.pdfItem = PDFItem.make(item: linkedItem) diff --git a/apple/OmnivoreKit/Sources/App/Views/Registration/NewAppleSignupView.swift b/apple/OmnivoreKit/Sources/App/Views/Registration/NewAppleSignupView.swift index 15f333b90..3bb561ac5 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Registration/NewAppleSignupView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Registration/NewAppleSignupView.swift @@ -1,4 +1,3 @@ -import Combine import Models import Services import SwiftUI @@ -8,8 +7,6 @@ import Views @MainActor final class NewAppleSignupViewModel: ObservableObject { @Published var loginError: LoginError? - var subscriptions = Set() - init() {} func submitProfile(userProfile: UserProfile, authenticator: Authenticator) async { diff --git a/apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift b/apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift index 32509e4e9..7ba4f8307 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WelcomeView.swift @@ -1,4 +1,3 @@ -import Combine import Models import Services import SwiftUI diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/AppleAuth.swift b/apple/OmnivoreKit/Sources/Services/Authentication/AppleAuth.swift index ae25129e9..751c53558 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/AppleAuth.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/AppleAuth.swift @@ -1,4 +1,3 @@ -import Combine import Foundation import Models diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index b35582b0d..0eb0d41ca 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -1,4 +1,3 @@ -import Combine import CoreData import CoreImage import Foundation @@ -19,7 +18,6 @@ public final class DataService: ObservableObject { var persistentContainer: PersistentContainer public var backgroundContext: NSManagedObjectContext - var subscriptions = Set() public var viewContext: NSManagedObjectContext { persistentContainer.viewContext diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/SavePDF.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/SavePDF.swift index 3e9d7787f..b6cf76259 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/SavePDF.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/SavePDF.swift @@ -1,4 +1,3 @@ -import Combine import Foundation import Models import SwiftGraphQL From 9c68802c443a31c5150c420c2c66efdfe619684b Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 21 Jun 2022 14:56:37 -0700 Subject: [PATCH 8/9] remove unused combine functions --- .../DataService/Networking/Networker.swift | 2 +- .../Networking/ServerResource.swift | 27 +------- .../CreateAccountResource.swift | 2 +- .../CreatePendingUserResource.swift | 2 +- .../VerifyAuthProviderToken.swift | 2 +- .../Sources/Views/KeyboardManagement.swift | 63 ------------------- 6 files changed, 5 insertions(+), 93 deletions(-) delete mode 100644 apple/OmnivoreKit/Sources/Views/KeyboardManagement.swift diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/Networker.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/Networker.swift index 5da9691de..f9d442616 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/Networker.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/Networker.swift @@ -39,7 +39,7 @@ extension Networker { ) do { - let authVerification = try await urlSession.performReq(resource: resource) + let authVerification = try await urlSession.performRequest(resource: resource) return authVerification.authStatus.isAuthenticated } catch { return false diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResource.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResource.swift index 5bdb206c7..331bac709 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResource.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResource.swift @@ -1,4 +1,3 @@ -import Combine import Foundation import Models import Utils @@ -37,7 +36,7 @@ extension ServerResponse { struct EmptyResponse: Decodable {} extension URLSession { - func performReq( + func performRequest( resource: ServerResource ) async throws -> ResponseModel { do { @@ -61,30 +60,6 @@ extension URLSession { throw ServerError(serverResponse: serverResponse) } } - - // TODO: remove performRequest - // swiftlint:disable:next line_length - func performRequest(resource: ServerResource) -> AnyPublisher { - let request = resource.urlRequest - - return dataTaskPublisher(for: resource.urlRequest) - .tryMap { data, response -> ResponseModel in - let serverResponse = ServerResponse(data: data, response: response) - NetworkRequestLogger.log(request: request, serverResponse: serverResponse) - - if let decodedValue = resource.decode(serverResponse) { - return decodedValue - } - - throw ServerError(serverResponse: serverResponse) - } - .mapError { error -> ServerError in - let serverResponse = ServerResponse(error: error) - NetworkRequestLogger.log(request: request, serverResponse: serverResponse) - return ServerError(serverResponse: serverResponse) - } - .eraseToAnyPublisher() - } } extension URLRequest { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateAccountResource.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateAccountResource.swift index 5e0b6f196..e144ea65a 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateAccountResource.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreateAccountResource.swift @@ -14,7 +14,7 @@ extension Networker { ) do { - return try await urlSession.performReq(resource: resource) + return try await urlSession.performRequest(resource: resource) } catch { throw (error as? ServerError) ?? .unknown } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift index 596bca4ca..e5f03e97b 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/CreatePendingUserResource.swift @@ -15,7 +15,7 @@ extension Networker { ) do { - return try await urlSession.performReq(resource: resource) + return try await urlSession.performRequest(resource: resource) } catch { throw (error as? ServerError) ?? .unknown } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAuthProviderToken.swift b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAuthProviderToken.swift index 52304dfbf..febc2f0cf 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAuthProviderToken.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Networking/ServerResources/VerifyAuthProviderToken.swift @@ -27,7 +27,7 @@ extension Networker { ) do { - return try await urlSession.performReq(resource: resource) + return try await urlSession.performRequest(resource: resource) } catch { if let error = error as? ServerError { throw LoginError.make(serverError: error) diff --git a/apple/OmnivoreKit/Sources/Views/KeyboardManagement.swift b/apple/OmnivoreKit/Sources/Views/KeyboardManagement.swift deleted file mode 100644 index ca0e42a3a..000000000 --- a/apple/OmnivoreKit/Sources/Views/KeyboardManagement.swift +++ /dev/null @@ -1,63 +0,0 @@ -import Combine -import SwiftUI - -#if !os(macOS) - import UIKit -#endif - -public extension Publishers { - static var keyboardHeight: AnyPublisher { - #if os(iOS) - let willShow = NotificationCenter.default - .publisher(for: UIApplication.keyboardWillShowNotification) - .map(\.keyboardHeight) - .eraseToAnyPublisher() - - let willHide = NotificationCenter.default - .publisher(for: UIApplication.keyboardWillHideNotification) - .map { _ in CGFloat(0) } - .eraseToAnyPublisher() - - return Merge(willShow, willHide) - .eraseToAnyPublisher() - #elseif os(macOS) - Future { $0(.success(CGFloat.zero)) } - .eraseToAnyPublisher() - #endif - } -} - -extension Notification { - var keyboardHeight: CGFloat { - #if os(iOS) - (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 - #elseif os(macOS) - 0 - #endif - } -} - -struct KeyboardAdaptive: ViewModifier { - @State private var keyboardHeight: CGFloat = 0 - - func body(content: Content) -> some View { - content - .padding(.bottom, keyboardHeight) - .onReceive(Publishers.keyboardHeight) { self.keyboardHeight = $0 } - } -} - -extension View { - func keyboardAdaptive() -> some View { - ModifiedContent(content: self, modifier: KeyboardAdaptive()) - } -} - -extension View { - func hideKeyboard() { - #if os(iOS) - UIApplication.shared - .sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - #endif - } -} From 311d694822a0a137643489d77b2f7fff60de8088 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 21 Jun 2022 16:21:21 -0700 Subject: [PATCH 9/9] update validate username publisher to async --- .../Registration/CreateProfileView.swift | 26 +- .../DataService/Mutations/SaveArticle.swift | 224 ------------------ .../Queries/ValidateUsernameQuery.swift | 23 +- 3 files changed, 23 insertions(+), 250 deletions(-) delete mode 100644 apple/OmnivoreKit/Sources/Services/DataService/Mutations/SaveArticle.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/Registration/CreateProfileView.swift b/apple/OmnivoreKit/Sources/App/Views/Registration/CreateProfileView.swift index db73ce795..2b8e71003 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Registration/CreateProfileView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Registration/CreateProfileView.swift @@ -53,29 +53,27 @@ import Views return } - dataService.validateUsernamePublisher(username: username).sink( - receiveCompletion: { [weak self] completion in - guard case let .failure(usernameError) = completion else { return } + Task { + do { + try await dataService.validateUsernamePublisher(username: username) + } catch { + let usernameError = (error as? UsernameAvailabilityError) ?? .unknown switch usernameError { case .tooShort: - self?.potentialUsernameStatus = .tooShort + potentialUsernameStatus = .tooShort case .tooLong: - self?.potentialUsernameStatus = .tooLong + potentialUsernameStatus = .tooLong case .invalidPattern: - self?.potentialUsernameStatus = .invalidPattern + potentialUsernameStatus = .invalidPattern case .nameUnavailable: - self?.potentialUsernameStatus = .unavailable + potentialUsernameStatus = .unavailable case .internalServer, .unknown: - self?.loginError = .unknown + loginError = .unknown case .network: - self?.loginError = .network + loginError = .network } - }, - receiveValue: { [weak self] in - self?.potentialUsernameStatus = .available } - ) - .store(in: &subscriptions) + } } func submitProfile(userProfile: UserProfile, authenticator: Authenticator) async { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/SaveArticle.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/SaveArticle.swift deleted file mode 100644 index 2c0766899..000000000 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/SaveArticle.swift +++ /dev/null @@ -1,224 +0,0 @@ -import Combine -import Foundation -import Models -import SwiftGraphQL - -public enum SaveArticleStatus { - case succeeeded - case processing(jobId: String) - case failed - - static func make(jobId: String, savingStatus: Enums.ArticleSavingRequestStatus) -> SaveArticleStatus { - switch savingStatus { - case .processing: - return .processing(jobId: jobId) - case .succeeded: - return .succeeeded - case .failed: - return .failed - } - } -} - -public extension Networker { - func articleSaveStatus(jobId: String) -> AnyPublisher { - enum QueryResult { - case saved(status: SaveArticleStatus) - case error(errorCode: Enums.ArticleSavingRequestErrorCode) - } - - let selection = Selection { - try $0.on( - articleSavingRequestError: .init { .error(errorCode: (try? $0.errorCodes().first) ?? .notFound) }, - articleSavingRequestSuccess: .init { - .saved( - status: try $0.articleSavingRequest( - selection: .init { - SaveArticleStatus.make( - jobId: try $0.id(), - savingStatus: try $0.status() - ) - } - ) - ) - } - ) - } - - let query = Selection.Query { - try $0.articleSavingRequest(id: jobId, selection: selection) - } - - let path = appEnvironment.graphqlPath - let headers = defaultHeaders - - return Deferred { - Future { promise in - send(query, to: path, headers: headers) { result in - switch result { - case let .success(payload): - if let graphqlError = payload.errors { - promise(.failure(.unknown(description: graphqlError.first.debugDescription))) - } - - switch payload.data { - case let .saved(status): - promise(.success(status)) - case let .error(errorCode: errorCode): - switch errorCode { - case .unauthorized: - promise(.failure(.unauthorized)) - case .notFound: - promise(.failure(.badData)) - } - } - case let .failure(error): - promise(.failure(SaveArticleError.make(from: error))) - } - } - } - } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } -} - -public extension DataService { - // swiftlint:disable:next function_body_length - func saveArticlePublisher( - pageScrapePayload: PageScrapePayload, - uploadFileId: String? - ) -> AnyPublisher { - enum MutationResult { - case saved(created: Bool) - case error(errorCode: Enums.CreateArticleErrorCode) - } - - let preparedDocument: InputObjects.PreparedDocumentInput? = { - if case let .html(html, title, _) = pageScrapePayload.contentType { - return InputObjects.PreparedDocumentInput( - document: html, - pageInfo: InputObjects.PageInfoInput(title: OptionalArgument(title)) - ) - } - return nil - }() - - let input = InputObjects.CreateArticleInput( - preparedDocument: OptionalArgument(preparedDocument), - uploadFileId: uploadFileId != nil ? .present(uploadFileId!) : .null(), - url: pageScrapePayload.url - ) - - let selection = Selection { - try $0.on( - createArticleError: .init { .error(errorCode: (try? $0.errorCodes().first) ?? .unableToParse) }, - createArticleSuccess: .init { .saved(created: try $0.created()) } - ) - } - - let mutation = Selection.Mutation { - try $0.createArticle(input: input, selection: selection) - } - - let path = appEnvironment.graphqlPath - let headers = networker.defaultHeaders - - return Deferred { - Future { promise in - send(mutation, to: path, headers: headers) { result in - switch result { - case let .success(payload): - if let graphqlError = payload.errors { - promise(.failure(.unknown(description: graphqlError.first.debugDescription))) - } - - switch payload.data { - case .saved: - promise(.success(())) - case let .error(errorCode: errorCode): - switch errorCode { - case .unauthorized: - promise(.failure(.unauthorized)) - default: - promise(.failure(.unknown(description: errorCode.rawValue))) - } - } - case let .failure(error): - promise(.failure(SaveArticleError.make(from: error))) - } - } - } - } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func saveArticlePublisher(articleURL: URL) -> AnyPublisher { - saveArticlePublisher(articleURLString: articleURL.absoluteString) - } - - func saveArticlePublisher(articleURLString: String) -> AnyPublisher { - enum MutationResult { - case saved(status: SaveArticleStatus) - case error(errorCode: Enums.CreateArticleSavingRequestErrorCode) - } - - let selection = Selection { - try $0.on( - createArticleSavingRequestError: .init { .error(errorCode: (try? $0.errorCodes().first) ?? .badData) }, - createArticleSavingRequestSuccess: .init { - .saved( - status: try $0.articleSavingRequest( - selection: .init { - SaveArticleStatus.make( - jobId: try $0.id(), - savingStatus: try $0.status() - ) - } - ) - ) - } - ) - } - - let mutation = Selection.Mutation { - try $0.createArticleSavingRequest( - input: InputObjects.CreateArticleSavingRequestInput(url: articleURLString), - selection: selection - ) - } - - let path = appEnvironment.graphqlPath - let headers = networker.defaultHeaders - - return Deferred { - Future { promise in - send(mutation, to: path, headers: headers) { result in - switch result { - case let .success(payload): - if let graphqlError = payload.errors { - promise(.failure(.unknown(description: graphqlError.first.debugDescription))) - } - - switch payload.data { - case let .saved(status): - promise(.success(status)) - case let .error(errorCode: errorCode): - switch errorCode { - case .unauthorized: - promise(.failure(.unauthorized)) - case .badData: - promise(.failure(.badData)) - } - } - case let .failure(error): - promise(.failure(SaveArticleError.make(from: error))) - } - } - } - } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } -} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ValidateUsernameQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ValidateUsernameQuery.swift index 07137b3ac..4d427950f 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ValidateUsernameQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ValidateUsernameQuery.swift @@ -1,30 +1,29 @@ -import Combine import Foundation import Models import SwiftGraphQL public extension DataService { - func validateUsernamePublisher(username: String) -> AnyPublisher { + func validateUsernamePublisher(username: String) async throws { let query = Selection.Query { try $0.validateUsername(username: username) } let path = appEnvironment.graphqlPath - return Deferred { - Future { promise in - send(query, to: path) { result in - switch result { - case let .success(payload): - promise(payload.data ? .success(()) : .failure(.nameUnavailable)) - case let .failure(error): - promise(.failure(UsernameAvailabilityError.make(from: error))) + return try await withCheckedThrowingContinuation { continuation in + send(query, to: path) { result in + switch result { + case let .success(payload): + if payload.data { + continuation.resume() + } else { + continuation.resume(throwing: UsernameAvailabilityError.nameUnavailable) } + case let .failure(error): + continuation.resume(throwing: UsernameAvailabilityError.make(from: error)) } } } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() } }