Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 0 additions & 14 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@ let package = Package(
],

dependencies: [
.package(
name: "ReSwift",
url: "https://github.com/ReSwift/ReSwift",
.upToNextMajor(from: "6.1.0")
),
.package(
name: "ReSwiftThunk",
url: "https://github.com/ReSwift/ReSwift-Thunk",
.upToNextMajor(from: "2.0.0")
),
.package(
name: "JWTDecode",
url: "https://github.com/auth0/JWTDecode.swift",
Expand Down Expand Up @@ -105,8 +95,6 @@ let package = Package(
name: "Rownd",
dependencies: [
"AnyCodable",
"ReSwift",
"ReSwiftThunk",
"JWTDecode",
"LBBottomSheet",
"Gzip",
Expand Down Expand Up @@ -134,8 +122,6 @@ let package = Package(
"Mocker",
"Mockingbird",
"AnyCodable",
"ReSwift",
"ReSwiftThunk",
"JWTDecode",
"LBBottomSheet",
"Gzip",
Expand Down
135 changes: 66 additions & 69 deletions Sources/Rownd/Models/AppleSignUpCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
// Created by Michael Murray on 7/17/22.
//

import SwiftUI
import AuthenticationServices
import UIKit
import AnyCodable
import ReSwiftThunk
import AuthenticationServices
import Combine
import SwiftUI
import UIKit

private let appleSignInDataKey = "userAppleSignInData"

Expand Down Expand Up @@ -140,33 +139,34 @@ class AppleSignUpCoordinator: NSObject, ASAuthorizationControllerDelegate, ASAut
// Prevent fast auth handshakes from feeling jarring to the user
try await Task.sleep(nanoseconds: UInt64(2 * Double(NSEC_PER_SEC)))

DispatchQueue.main.async {
Context.currentContext.store.dispatch(Context.currentContext.store.state.auth.onReceiveAppleAuthTokens(
AuthState(
accessToken: tokenResponse?.accessToken,
refreshToken: tokenResponse?.refreshToken
)
))
// Handle auth tokens
let newAuthState = AuthState(
accessToken: tokenResponse?.accessToken,
refreshToken: tokenResponse?.refreshToken
)
newAuthState.onReceiveAppleAuthTokens(newAuthState)

await Context.currentContext.store.setLastSignInMethod(.apple)

// Subscribe to auth state to update user data when valid
Context.currentContext.store.publisher(for: \.auth.isAccessTokenValid)
.filter { $0 }
.first()
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.updateUserDataWithAppleData(fullName: fullName, email: email)

RowndEventEmitter.emit(RowndEvent(
event: .signInCompleted,
data: [
"method": AnyCodable(SignInType.apple.rawValue),
"user_type": AnyCodable(tokenResponse?.userType?.rawValue),
"app_variant_user_type": AnyCodable(tokenResponse?.appVariantUserType?.rawValue)
]
))
}
.store(in: &self.cancellables)

Context.currentContext.store.dispatch(SetLastSignInMethod(payload: SignInMethodTypes.apple))

let subscriber = Context.currentContext.store.subscribe { $0.auth.isAccessTokenValid }
subscriber.$current.sink { isAccessTokenValid in
if isAccessTokenValid {
subscriber.unsubscribe()
self.updateUserDataWithAppleData(fullName: fullName, email: email)

RowndEventEmitter.emit(RowndEvent(
event: .signInCompleted,
data: [
"method": AnyCodable(SignInType.apple.rawValue),
"user_type": AnyCodable(tokenResponse?.userType?.rawValue),
"app_variant_user_type": AnyCodable(tokenResponse?.appVariantUserType?.rawValue)
]
))
}
}.store(in: &self.cancellables)
}
} catch ApiError.generic(let errorInfo) {
if errorInfo.code == "E_SIGN_IN_USER_NOT_FOUND" {
Task { @MainActor in
Expand Down Expand Up @@ -212,58 +212,55 @@ class AppleSignUpCoordinator: NSObject, ASAuthorizationControllerDelegate, ASAut
}

func updateUserDataWithAppleData(fullName: PersonNameComponents?, email: String?) {
Context.currentContext.store.dispatch(Thunk<RowndState> { dispatch, getState in
guard let state = getState() else { return }
Task {
do {
if let userStateResponse = try await UserData.fetchUserData(state) {
var userData = state.user.data
userData.merge(userStateResponse.data) { (current, _) in current }

let defaults = UserDefaults.standard
// use UserDefault values for Email and fullName if available
if let userAppleSignInData = defaults.object(forKey: appleSignInDataKey) as? Data {
let decoder = JSONDecoder()
if let loadedAppleSignInData = try? decoder.decode(AppleSignInData.self, from: userAppleSignInData) {
userData["email"] = AnyCodable(loadedAppleSignInData.email)
userData["first_name"] = AnyCodable(loadedAppleSignInData.firstName)
userData["last_name"] = AnyCodable(loadedAppleSignInData.lastName)
userData["full_name"] = AnyCodable(loadedAppleSignInData.fullName)
}

// Remove the data since we no longer need it for subsequent signins.
defaults.removeObject(forKey: appleSignInDataKey)
} else {
if let email = email {
userData["email"] = AnyCodable(email)
userData["first_name"] = AnyCodable(fullName?.givenName)
userData["last_name"] = AnyCodable(fullName?.familyName)
userData["full_name"] = AnyCodable(String("\(fullName?.givenName) \(fullName?.familyName)"))
}
}
Task {
do {
if let userStateResponse = try await UserData.fetchUserData() {
var userData = Context.currentContext.store.state.user.data
userData.merge(userStateResponse.data) { (current, _) in current }

if !userData.isEmpty {
dispatch(UserData.save(userData))
logger.debug("UserData to save after signin: \(String(describing: userData))")
let defaults = UserDefaults.standard
// use UserDefault values for Email and fullName if available
if let userAppleSignInData = defaults.object(forKey: appleSignInDataKey) as? Data {
let decoder = JSONDecoder()
if let loadedAppleSignInData = try? decoder.decode(AppleSignInData.self, from: userAppleSignInData) {
userData["email"] = AnyCodable(loadedAppleSignInData.email)
userData["first_name"] = AnyCodable(loadedAppleSignInData.firstName)
userData["last_name"] = AnyCodable(loadedAppleSignInData.lastName)
userData["full_name"] = AnyCodable(loadedAppleSignInData.fullName)
}

// Remove the data since we no longer need it for subsequent signins.
defaults.removeObject(forKey: appleSignInDataKey)
} else {
// Handle the case where userStateResponse is nil
logger.error("Failed to fetch user state response")
if let email = email {
userData["email"] = AnyCodable(email)
userData["first_name"] = AnyCodable(fullName?.givenName)
userData["last_name"] = AnyCodable(fullName?.familyName)
userData["full_name"] = AnyCodable(String("\(fullName?.givenName ?? "") \(fullName?.familyName ?? "")"))
}
}

if !userData.isEmpty {
await UserData.save(userData)
logger.debug("UserData to save after signin: \(String(describing: userData))")
}
} catch {
// Handle any errors that occurred during fetch
logger.error("Error fetching user data: \(error)")
} else {
// Handle the case where userStateResponse is nil
logger.error("Failed to fetch user state response")
}
} catch {
// Handle any errors that occurred during fetch
logger.error("Error fetching user data: \(error)")
}
})
}
}

// If authorization faced any issue then this method will get triggered
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {

// If there is any error will get it here
logger.error("An error occurred while signing in with Apple. Error: \(String(describing: error))")

func defaultSignInFlow() {
logger.error("Falling back to default sign flow")
Rownd.requestSignIn(RowndSignInOptions(intent: intent))
Expand Down
39 changes: 22 additions & 17 deletions Sources/Rownd/Models/Automations/AutomationsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
//

import AnyCodable
import Combine
import Foundation
import ReSwift

public struct AutomationStoreState {
public struct AutomationStoreState: Sendable {
var user: UserState
var automations: [RowndAutomation]?
var auth: AuthState
Expand All @@ -34,11 +34,11 @@ func computeLastRunTimestamp(automation: RowndAutomation, meta: [String: AnyCoda
return nil
}

public class AutomationsCoordinator: NSObject, StoreSubscriber {
public class AutomationsCoordinator: NSObject {
private var state: AutomationStoreState?
public typealias StoreSubscriberStateType = AutomationStoreState
let debouncer = Debouncer(delay: 0.5) // 500ms
private var isStarted = false
private var cancellable: AnyCancellable?

override init() {
super.init()
Expand All @@ -48,29 +48,34 @@ public class AutomationsCoordinator: NSObject, StoreSubscriber {
public func start() {
guard !isStarted else { return }
isStarted = true
Context.currentContext.store.subscribe(self) {
$0.select {

// Subscribe to state changes using Combine
cancellable = Context.currentContext.store.publisher()
.map { state in
AutomationStoreState(
user: $0.user, automations: $0.appConfig.config?.automations, auth: $0.auth,
passkeys: $0.passkeys)
user: state.user,
automations: state.appConfig.config?.automations,
auth: state.auth,
passkeys: state.passkeys
)
}
.receive(on: DispatchQueue.main)
.sink { [weak self] newState in
self?.state = newState
self?.processAutomations()
}
}
}

@MainActor
public func stop() {
guard isStarted else { return }
Context.currentContext.store.unsubscribe(self)
cancellable?.cancel()
cancellable = nil
isStarted = false
}

deinit {
DispatchQueue.main.sync { [weak self] in self?.stop() }
}

public func newState(state: AutomationStoreState) {
self.state = state
self.processAutomations()
cancellable?.cancel()
}

private func processAutomations(_ state: AutomationStoreState) {
Expand Down Expand Up @@ -129,7 +134,7 @@ public class AutomationsCoordinator: NSObject, StoreSubscriber {

actionFn(args)

// Save automatino action in meta data
// Save automation action in meta data
let lastRunId = computeLastRunId(automation)
Task { @MainActor in
let date = NetworkTimeManager.shared.currentTime ?? Date()
Expand Down
Loading
Loading