363 lines
16 KiB
Swift
363 lines
16 KiB
Swift
//
|
|
// MainService.swift
|
|
// Wallet
|
|
//
|
|
// Created by Igor on 05.03.2021.
|
|
// Copyright © 2021 AM. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import Alamofire
|
|
import EosioSwift
|
|
import WalletNetwork
|
|
|
|
import struct Resolver.Injected
|
|
import class Resolver.ResolverScope
|
|
|
|
final class RuntimeConfiguration {
|
|
// var serverTime: Date = Date()
|
|
var serverTimeDiff: Double = 0
|
|
var currentTimestamp: Double { Date().timeIntervalSince1970 + serverTimeDiff }
|
|
var dealsActiveOnly = true
|
|
}
|
|
|
|
extension Main {
|
|
final class Service {
|
|
|
|
struct Socket2Msg: Decodable {
|
|
let payload: Socket2MsgPayload
|
|
|
|
init(json: String) throws {
|
|
self = try JSONDecoder().decode(Self.self, from: Data(json.utf8))
|
|
}
|
|
}
|
|
struct Socket2MsgPayload: Decodable {
|
|
let data: Socket2MsgPayloadData
|
|
}
|
|
struct Socket2MsgPayloadData: Decodable {
|
|
let notifications: Socket2MsgNotification
|
|
}
|
|
struct Socket2MsgNotification: Decodable {
|
|
let notificationType: String
|
|
let state: String
|
|
let payload: String
|
|
let previousUid: String
|
|
let currentUid: String
|
|
|
|
var order: P2POrder? {
|
|
try? JSONDecoder().decode(P2POrder.self, from: Data(payload.utf8))
|
|
}
|
|
var deal: P2PDeal? {
|
|
try? JSONDecoder().decode(P2PDeal.self, from: Data(payload.utf8))
|
|
}
|
|
var balance: P2PBalance? {
|
|
try? JSONDecoder().decode(P2PBalance.self, from: Data(payload.utf8))
|
|
}
|
|
}
|
|
|
|
@Injected var runtimeConfiguration: RuntimeConfiguration
|
|
@Injected var ordersService: P2POrdersService
|
|
@Injected var dealsService: P2PMyDealsService
|
|
@Injected var p2pBalancesService: P2PBalancesService
|
|
@Injected var p2pListsService: P2PListsService
|
|
|
|
init() {
|
|
p2pListsService.fetch(completion: { _ in })
|
|
}
|
|
|
|
var previousUid: String?
|
|
var appSettings: AppSettingsModel {
|
|
get async {
|
|
if let settings { return settings }
|
|
let settings = await self.loadAppSettings()
|
|
self.settings = settings
|
|
return settings
|
|
}
|
|
}
|
|
|
|
private var settings: AppSettingsModel?
|
|
|
|
func processSocket(info: [AnyHashable: Any]?) {
|
|
guard let info = info as? [String: String] else {
|
|
return
|
|
}
|
|
if let json = info["data"] {
|
|
if let socketMsg = try? Socket2Msg(json: json) {
|
|
// print(previousUid)
|
|
// print(socketMsg.payload.data.notifications.previousUid)
|
|
// print(socketMsg.payload.data.notifications.currentUid)
|
|
|
|
if let previousUid = previousUid, previousUid != socketMsg.payload.data.notifications.previousUid {
|
|
reloadP2PData(username: Accounts().current?.name ?? "")
|
|
}
|
|
previousUid = socketMsg.payload.data.notifications.currentUid
|
|
|
|
switch socketMsg.payload.data.notifications.notificationType.uppercased() {
|
|
case "P2P_ORDERS":
|
|
switch socketMsg.payload.data.notifications.state.uppercased() {
|
|
case "CREATED", "UPDATED":
|
|
if !ordersService.updateOrCreate(order: socketMsg.payload.data.notifications.order) {
|
|
reloadP2PData(username: Accounts().current?.name ?? "")
|
|
}
|
|
case "DELETED":
|
|
if !ordersService.delete(order: socketMsg.payload.data.notifications.order) {
|
|
reloadP2PData(username: Accounts().current?.name ?? "")
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
case "P2P_DEALS":
|
|
switch socketMsg.payload.data.notifications.state.uppercased() {
|
|
case "CREATED", "UPDATED":
|
|
if !dealsService.updateOrCreate(deal: socketMsg.payload.data.notifications.deal) {
|
|
reloadP2PData(username: Accounts().current?.name ?? "")
|
|
}
|
|
case "DELETED":
|
|
if !dealsService.delete(deal: socketMsg.payload.data.notifications.deal) {
|
|
reloadP2PData(username: Accounts().current?.name ?? "")
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
case "P2P_I_PAID":
|
|
if !dealsService.updateOrCreate(deal: socketMsg.payload.data.notifications.deal) {
|
|
reloadP2PData(username: Accounts().current?.name ?? "")
|
|
}
|
|
case "P2P_DEPOSITS":
|
|
if !p2pBalancesService.updateOrCreate(balance: socketMsg.payload.data.notifications.balance) {
|
|
reloadP2PData(username: Accounts().current?.name ?? "")
|
|
}
|
|
case "CHATS":
|
|
Account.Service.History.shared.current?.fetch()
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func reloadP2PData(username: String) {
|
|
// ordersService.fetch(username: username) { _ in }
|
|
// dealsService.fetch(username: username) { _ in }
|
|
// p2pBalancesService.fetch(username: username) { _ in }
|
|
|
|
ordersService.fetch(username: username) {
|
|
if $0 == nil {
|
|
self.dealsService.fetch(username: username) {
|
|
if $0 == nil {
|
|
self.p2pBalancesService.fetch(username: username) {
|
|
if $0 == nil {
|
|
} else {
|
|
delayed(5, { self.reloadP2PData(username: username) })
|
|
}
|
|
}
|
|
} else {
|
|
delayed(5, { self.reloadP2PData(username: username) })
|
|
}
|
|
}
|
|
} else {
|
|
delayed(5, { self.reloadP2PData(username: username) })
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadNodes() async {
|
|
|
|
let record = ApplicationEnvironment.shared().current
|
|
guard let environment = try? record.networkEnvironment() else { return }
|
|
let service = EOSService(environment: environment)
|
|
let nodes = await service.requireNodes()
|
|
|
|
ApplicationEnvironment.shared().current.update(nodes: nodes)
|
|
}
|
|
|
|
func loadVersion() async -> String? {
|
|
|
|
let results = await withTaskGroup(of: (String, TimeInterval, Int, Int, Int, Int).self) { taskGroup -> [(String, TimeInterval, Int, Int, Int, Int)] in
|
|
let systemName = await UIDevice.current.systemName
|
|
|
|
for node in ApplicationEnvironment.shared().current.nodes {
|
|
taskGroup.addTask {
|
|
let startTime = Date().timeIntervalSince1970
|
|
let result = await Network.Service.Blockchain
|
|
.queryRaw(endpoint: node,
|
|
contract: .appSettings,
|
|
scope: ApplicationEnvironment.shared().current.contract(.appSettings),
|
|
table: "build")
|
|
let timeSpent = Date().timeIntervalSince1970 - startTime
|
|
print("Node \(node) loaded version in \(timeSpent) seconds")
|
|
|
|
if let rows = result.value,
|
|
let minBuildVersionString = rows.compactMap({ $0["min_version_build"] as? String }).first,
|
|
let minBuildVersionJson = (try? JSONSerialization.jsonObject(with: minBuildVersionString.data(using: .utf8) ?? Data(), options: [])) as? [String: Any],
|
|
let minBuildVersion = minBuildVersionJson[systemName] as? String,
|
|
let major = Int(minBuildVersion.split(separator: ".").first ?? ""),
|
|
let minor = Int(minBuildVersion.split(separator: ".").dropFirst().first ?? ""),
|
|
let patch = Int(minBuildVersion.split(separator: ".").dropFirst().dropFirst().first ?? ""),
|
|
let build = Int(minBuildVersion.split(separator: ".").dropFirst().dropFirst().dropFirst().first ?? "0") {
|
|
return (node, timeSpent, major, minor, patch, build)
|
|
}
|
|
return (node, 1000, 1000, 1000, 1000, 1000)
|
|
}
|
|
}
|
|
|
|
var childTaskResults = [(String, TimeInterval, Int, Int, Int, Int)]()
|
|
for await result in taskGroup {
|
|
if !childTaskResults.isEmpty {
|
|
print("Too late for version from \(result.0) some node was faster \(childTaskResults.sorted(by: { $0.1 < $1.1 }).map({ $0.0 }).joined(separator: ", "))")
|
|
}
|
|
childTaskResults.append(result)
|
|
if result.2 < 1000 {
|
|
ApplicationEnvironment.shared().current
|
|
.update(nodes: [result.0] + ApplicationEnvironment.shared().current.nodes.filter({ $0 != result.0 }))
|
|
return [result]
|
|
}
|
|
}
|
|
|
|
return childTaskResults
|
|
}
|
|
// let node = results.sorted(by: { $0.1 < $1.1 })
|
|
|
|
if let result = results.first(where: { $0.2 < 1000 }) {
|
|
if Bundle.main.major < result.2 {
|
|
return L10n.Main.wrongVersion
|
|
} else if Bundle.main.major == result.2 {
|
|
if Bundle.main.minor < result.3 {
|
|
return L10n.Main.wrongVersion
|
|
} else if Bundle.main.minor == result.3 {
|
|
if Bundle.main.patch < result.4 {
|
|
return L10n.Main.wrongVersion
|
|
} else if Bundle.main.patch == result.4 {
|
|
if Bundle.main.build < result.5 {
|
|
return L10n.Main.wrongVersion
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return L10n.Main.noVersion
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private func loadAppSettings() async -> AppSettingsModel {
|
|
|
|
guard let node = ApplicationEnvironment.shared().current.nodes.first else { return AppSettingsModel() }
|
|
let result = await Network.Service
|
|
.Blockchain
|
|
.queryRaw(endpoint: node,
|
|
contract: .appSettings,
|
|
scope: ApplicationEnvironment.shared().current.contract(.appSettings),
|
|
table: "config")
|
|
|
|
guard let rows = result.value else { return AppSettingsModel() }
|
|
|
|
let iosData = rows.compactMap { row -> [String: Any]? in
|
|
if row["device"] as? String == "ios" {
|
|
return row
|
|
}
|
|
return nil
|
|
}.first
|
|
|
|
if let value = iosData?["data"] as? String,
|
|
let data = value.data(using: .utf8),
|
|
let configParams = try? JSONDecoder().decode(AppSettingsModel.self,
|
|
from: data) {
|
|
return configParams
|
|
}
|
|
|
|
return AppSettingsModel()
|
|
}
|
|
|
|
func loadServerTime() {
|
|
|
|
guard let node = ApplicationEnvironment.shared().current.node,
|
|
let url = URL(string: node) else {
|
|
return
|
|
}
|
|
|
|
EosioRpcProvider(endpoint: url).getInfo(completion: { [weak self] (result: EosioResult<EosioRpcInfoResponse, EosioError>) in
|
|
switch result {
|
|
case let .success(response):
|
|
// self?.runtimeConfiguration.serverTime = response.headBlockTime.asISODate() ?? Date()
|
|
self?.runtimeConfiguration.serverTimeDiff = (response.headBlockTime.asISODate() ?? Date()).timeIntervalSince1970 - Date().timeIntervalSince1970
|
|
case .failure:
|
|
break
|
|
}
|
|
})
|
|
}
|
|
|
|
func loadHyperions() {
|
|
let groupFailed = DispatchGroup()
|
|
let queue = DispatchQueue(label: "")
|
|
var success = false
|
|
|
|
for node in ApplicationEnvironment.shared().current.nodes {
|
|
groupFailed.enter()
|
|
let startTime = Date().timeIntervalSince1970
|
|
Network.Service.Blockchain.queryRaw(endpoint: node, contract: .appSettings, scope: ApplicationEnvironment.shared().current.contract(.appSettings), table: "data", closure: {
|
|
switch $0 {
|
|
case let .success(rows):
|
|
DispatchQueue.main.async {
|
|
print("Node \(node) loaded hyperions in \(Date().timeIntervalSince1970 - startTime) seconds")
|
|
}
|
|
queue.async {
|
|
print(rows)
|
|
print(rows.compactMap({ $0["endpoint"] as? String }))
|
|
if rows.compactMap({ $0["endpoint"] as? String }).count > 0 {
|
|
guard !success else {
|
|
print("Too late for hyperions from \(node) some node was faster \(ApplicationEnvironment.shared().current.node.orCreate(""))")
|
|
return
|
|
}
|
|
success = true
|
|
|
|
ApplicationEnvironment.shared().current
|
|
.update(hyperions: rows.compactMap({ $0["endpoint"] as? String }).shuffled())
|
|
|
|
ApplicationEnvironment.shared().current
|
|
.update(nodes: [node] + ApplicationEnvironment.shared().current.nodes.filter({ $0 != node }))
|
|
DispatchQueue.main.async {
|
|
self.loadServerTime()
|
|
Account.Service.History.shared.fetch()
|
|
ResolverScope.userSession.reset()
|
|
Notification.post(name: .didChangeAccount)
|
|
}
|
|
} else {
|
|
groupFailed.leave()
|
|
}
|
|
}
|
|
case let .failure(error):
|
|
print(error)
|
|
groupFailed.leave()
|
|
}
|
|
})
|
|
}
|
|
|
|
groupFailed.notify(queue: .main) {
|
|
print("All Tasks are failed")
|
|
Alert.success(text: L10n.Main.noHyperion)
|
|
}
|
|
}
|
|
|
|
func loadNews() async -> [GetNewsGraphQLRequest.News] {
|
|
let variables = GetNewsGraphQLRequest.Variables(langCode: Common.Model.Language.current.rawValue)
|
|
|
|
let backend = ApplicationEnvironment.shared().current.backend
|
|
let news = try? await AF.request(
|
|
backend.urlPath,
|
|
method: .post,
|
|
parameters: GetNewsGraphQLRequest(variables: variables),
|
|
encoder: JSONParameterEncoder.default,
|
|
headers: HTTPHeaders(backend.headers)
|
|
)
|
|
.serializingDecodable(GraphQLResponse<GetNewsGraphQLRequest.ResponseData>.self)
|
|
.value.data?.getNews ?? []
|
|
|
|
print(news?.filter({ $0.deviceType.lowercased() == "ios" || $0.deviceType.lowercased() == "any" }))
|
|
print(news)
|
|
|
|
return news?.filter({ $0.deviceType.lowercased() == "ios" || $0.deviceType.lowercased() == "any" }) ?? []
|
|
}
|
|
}
|
|
}
|