-
Notifications
You must be signed in to change notification settings - Fork 0
/
WealthsimpleDownloader.swift
153 lines (138 loc) · 5.79 KB
/
WealthsimpleDownloader.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//
// WealthsimpleDownloader.swift
//
//
// Created by Steffen Kötte on 2020-07-12.
//
import Foundation
/// Protocol to save API tokens
///
/// Can for example be implemented using Keychain on Apple devices
public protocol CredentialStorage {
/// Save a value to the store
/// - Parameters:
/// - value: value
/// - key: key to retrieve in later
func save(_ value: String, for key: String)
/// Retrieve a value
/// - Parameter key: key under which the value was stored
///
/// - Returns: The saved value or nil if no value was found
func read(_ key: String) -> String?
}
/// Main entry point for the library
public final class WealthsimpleDownloader {
/// Callback which is called in case the user needs to authenticate. Needs to return username, password, and one time password
public typealias AuthenticationCallback = (@escaping (String, String, String) -> Void) -> Void
private let authenticationCallback: AuthenticationCallback
private let credentialStorage: CredentialStorage
private var token: Token?
/// Creates the Downloader instance
///
/// After creating, first call the authenticate method.
///
/// - Parameters:
/// - authenticationCallback: Callback which is called in case the user needs to authenticate.
/// Needs to return username, password, and one time password. Might be called during any call.
/// - credentialStorage: A CredentialStore to save API tokens to. Implementation can be empty,
/// in this case the authenticationCallback will be called every time and not only when the refresh token expired
public init(authenticationCallback: @escaping AuthenticationCallback, credentialStorage: CredentialStorage) {
self.authenticationCallback = authenticationCallback
self.credentialStorage = credentialStorage
}
/// Authneticates against the API. Call before calling any other method.
/// - Parameter completion: Gets an error in case something went wrong, otherwise nil
public func authenticate(completion: @escaping (Error?) -> Void) {
if let token {
token.refreshIfNeeded {
switch $0 {
case .failure:
self.getNewToken(completion: completion)
case let .success(newToken):
self.token = newToken
completion(nil)
}
}
return
} else {
Token.getToken(from: credentialStorage) {
if let token = $0 {
self.token = token
completion(nil)
return
} else {
self.token = nil
self.getNewToken(completion: completion)
return
}
}
}
}
/// Get all Accounts the user has access to
/// - Parameter completion: Result with an array of `Account`s or an `Account.AccountError`
public func getAccounts(completion: @escaping (Result<[Account], AccountError>) -> Void) {
guard let token else {
completion(.failure(.tokenError(.noToken)))
return
}
WealthsimpleAccount.getAccounts(token: token) {
if case let .failure(error) = $0 {
if case .tokenError = error {
self.token = nil
}
}
completion($0)
}
}
/// Get all `Position`s from one `Account`
/// - Parameters:
/// - account: Account to retreive positions for
/// - date: Date of which the positions should be downloaded. If not date is provided, not date is sent to the API. The API falls back to the current date.
/// - completion: Result with an array of `Position`s or an `Position.PositionError`
public func getPositions(in account: Account, date: Date?, completion: @escaping (Result<[Position], PositionError>) -> Void) {
guard let token else {
completion(.failure(.tokenError(.noToken)))
return
}
WealthsimplePosition.getPositions(token: token, account: account, date: date) {
if case let .failure(error) = $0 {
if case .tokenError = error {
self.token = nil
}
}
completion($0)
}
}
/// Get all `Transactions`s from one `Account`
/// - Parameters:
/// - account: Account to retreive transactions from
/// - startDate: Date from which the transactions are downloaded. If not date is provided, not date is sent to the API. The API falls back to 30 days ago from today.
/// - completion: Result with an array of `Transactions`s or an `Transactions.TransactionsError`
public func getTransactions(in account: Account, startDate: Date?, completion: @escaping (Result<[Transaction], TransactionError>) -> Void) {
guard let token else {
completion(.failure(.tokenError(.noToken)))
return
}
WealthsimpleTransaction.getTransactions(token: token, account: account, startDate: startDate) {
if case let .failure(error) = $0 {
if case .tokenError = error {
self.token = nil
}
}
completion($0)
}
}
private func getNewToken(completion: @escaping (Error?) -> Void) {
authenticationCallback { username, password, otp in
Token.getToken(username: username, password: password, otp: otp, credentialStorage: self.credentialStorage) {
switch $0 {
case let .failure(error):
completion(error)
case let .success(token):
self.token = token
completion(nil)
}
}
}
}
}