// // AccountServiceCreateAccount.swift // Wallet // // Created by Saveliy Stavitsky on 12/10/20. // Copyright © 2020 List. All rights reserved. // import Foundation import Alamofire import DeviceCheck import WalletNetwork typealias JSON = [String: Any] extension JSON { func asData() -> Data? { try? JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) } } extension AccountOnboarding.Service { class CreateAccount { private(set) var usernames: [String] = [] private func fetchDeviceToken(completion: @escaping (String?,NSError?) -> Void) { guard DCDevice.current.isSupported else { completion(nil, .error(L10n.Error.Device.support));return } DCDevice.current.generateToken { guard $1 == nil, let data = $0 else { completion(nil, .error(L10n.Error.Device.token));return } let result = [ "deviceType": "ios", "payload": [ "device_token": data.base64EncodedString() ] ].asData()?.base64EncodedString() DispatchQueue.main.async { completion(result ?? "", nil) } } } struct CreateRequest: Codable { let query: String let variables: CreateRequestVariables? let operationName: String? } struct CreateRequestVariables: Codable { let publicKey: String let username: String let payload: String } func submitUsername(username: String, publicKey: String, completion: @escaping (Result) -> Void) { fetchDeviceToken { (token, error) in guard let token = token else { completion(.failure(error?.localizedDescription ?? L10n.Common.Error.default)) return } let environment = ApplicationEnvironment.shared().current AF.request( environment.backend.urlPath, method: .post, parameters: CreateRequest( query: #""" mutation createAccount($publicKey:String!, $username:String!, $payload:String!) { createAccount(publicKey:$publicKey, username:$username, payload:$payload) { account { username } errors } } """#, variables: CreateRequestVariables(publicKey: publicKey, username: username, payload: token), operationName: "createAccount"), encoder: JSONParameterEncoder.default, headers: HTTPHeaders(environment.headers) ).responseDecodable(of: GraphQLResponse.self) { (response) in if let username = response.value?.data?.createAccount.account?.username { return completion(.success(username)) } else { return completion(.failure(response.value?.data?.createAccount.errors ?? "")) } } } } private struct CreateResponseData: Decodable { let createAccount: CreateData } private struct CreateData: Decodable { let account: Account? let errors: String? } } } extension AccountOnboarding.Service.CreateAccount { func fetchUsernames(_ completion: @escaping ([String]) -> Void) { let environment = ApplicationEnvironment.shared().current AF.request( environment.backend.urlPath, method: .post, parameters: ["query": "query { accountOrder { accounts { username } } }"], encoder: JSONParameterEncoder.default, headers: HTTPHeaders(environment.headers) ).responseDecodable(of: GraphQLResponse.self) { (response) in completion(response.value?.data?.accountOrder.accounts.map({ $0.username }) ?? []) } } private struct UsernamesResponseData: Decodable { let accountOrder: Order } private struct Order: Decodable { let accounts: [Account] } private struct Account: Decodable { let username: String } } extension AccountOnboarding.Service.CreateAccount { func checkUsername(username: String) async -> Bool { let record = ApplicationEnvironment.shared().current guard let environment = try? record.networkEnvironment() else { return false } let service = AccountService(environment: environment) switch await service.isAvailable(username: username) { case .available: return true case .alreadyTaken, .system: return false } } } extension AccountOnboarding.Service.CreateAccount { func startWalletPurchase(username: String, publicKey: String, completion: @escaping (Result) -> Void) { let environment = ApplicationEnvironment.shared().current AF.request( environment.backend.urlPath, method: .post, parameters: CreateRequest( query: #""" mutation { startWalletPurchase(username:"some_username", pubKey:"public_key", device:IOS) { tempToken errors } } """# .replacingOccurrences(of: "some_username", with: username) .replacingOccurrences(of: "public_key", with: publicKey), variables: nil, operationName: nil), encoder: JSONParameterEncoder.default, headers: HTTPHeaders(environment.headers) ) .responseDecodable(of: GraphQLResponse.self) { (response) in if let tempToken = response.value?.data?.startWalletPurchase?.tempToken { return completion(.success(tempToken)) } else { return completion(.failure(response.value?.data?.startWalletPurchase?.errors ?? response.value?.errorsDescription ?? "")) } } } private struct StartWalletPurchaseData: Decodable { let startWalletPurchase: StartWalletPurchaseResult? } private struct StartWalletPurchaseResult: Decodable { let tempToken: String? let errors: String? } } extension AccountOnboarding.Service.CreateAccount { func verifyPurchase(tempToken: String, token: String, completion: @escaping (Result) -> Void) { let environment = ApplicationEnvironment.shared().current AF.request( environment.backend.urlPath, method: .post, parameters: CreateRequest( query: #""" query { verifyPurchaseToken(tempToken:"temp_token", token:"token_token") { errors } } """# .replacingOccurrences(of: "temp_token", with: tempToken) .replacingOccurrences(of: "token_token", with: token), variables: nil, operationName: nil), encoder: JSONParameterEncoder.default, headers: HTTPHeaders(environment.headers) ) .responseDecodable(of: GraphQLResponse.self) { (response) in if let errors = response.value?.data?.verifyPurchaseToken?.errors ?? response.value?.errorsDescription { return completion(.failure(errors)) } else { return completion(.success(true)) } } } private struct VerifyPurchaseTokenData: Decodable { let verifyPurchaseToken: VerifyPurchaseTokenResult? } private struct VerifyPurchaseTokenResult: Decodable { let errors: String? } } extension AccountOnboarding.Service.CreateAccount { func completePurchase(tempToken: String, token: String, completion: @escaping (Result) -> Void) { let environment = ApplicationEnvironment.shared().current AF.request( environment.backend.urlPath, method: .post, parameters: CreateRequest( query: #""" mutation { completePurchase(tempToken:"temp_token", token:"token_token") { errors } } """# .replacingOccurrences(of: "temp_token", with: tempToken) .replacingOccurrences(of: "token_token", with: token), variables: nil, operationName: nil), encoder: JSONParameterEncoder.default, headers: HTTPHeaders(environment.headers) ) // .responseString(completionHandler: { // print(String(data: $0.data!, encoding: .utf8)) // print(String(data: $0.data!, encoding: .utf8)) // }) .responseDecodable(of: GraphQLResponse.self) { (response) in if let errors = response.value?.data?.completePurchase?.errors ?? response.value?.errorsDescription { return completion(.failure(errors)) } else { return completion(.success(true)) } } } private struct CompletePurchaseData: Decodable { let completePurchase: CompletePurchaseResult? } private struct CompletePurchaseResult: Decodable { let errors: String? } }