185 lines
7.2 KiB
Swift
185 lines
7.2 KiB
Swift
//
|
|
// OnBoardingService.swift
|
|
// Malinka
|
|
//
|
|
// Created by Juraldinio on 12/15/22.
|
|
// Copyright © 2022 NUT.Tech. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import EosioSwift
|
|
import Combine
|
|
import WalletNetwork
|
|
|
|
struct OnBoardingService {
|
|
|
|
enum ServiceError: Error {
|
|
case nodes(cause: String)
|
|
case hyperions(cause: String)
|
|
case version(cause: String)
|
|
}
|
|
|
|
let environment: ApplicationEnvironment
|
|
|
|
func loadVersion() async throws {
|
|
|
|
struct Version {
|
|
|
|
static let invalid = 1000
|
|
|
|
let node: String
|
|
let timeSpent: TimeInterval
|
|
let major: Int
|
|
let minor: Int
|
|
let patch: Int
|
|
let build: Int
|
|
|
|
var isValid: Bool { self.major != Self.invalid }
|
|
|
|
static func createInvalid(for node: String) -> Version {
|
|
Version(node: node, timeSpent: 0, major: Self.invalid, minor: Self.invalid, patch: Self.invalid, build: Self.invalid)
|
|
}
|
|
}
|
|
|
|
let systemName = await UIDevice.current.systemName
|
|
let nodes = self.environment.current.nodes
|
|
|
|
let results = await withTaskGroup(of: Version.self) { taskGroup -> [Version] in
|
|
|
|
nodes.forEach { node in
|
|
taskGroup.addTask {
|
|
let startTime = Date().timeIntervalSince1970
|
|
let result = await Network.Service.Blockchain
|
|
.queryRaw(endpoint: node,
|
|
contract: .appSettings,
|
|
scope: self.environment.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 versions = (minBuildVersionJson[systemName] as? String)?.split(separator: "."),
|
|
let major = Int(versions.first ?? ""),
|
|
let minor = Int(versions.dropFirst().first ?? ""),
|
|
let patch = Int(versions.dropFirst().dropFirst().first ?? ""),
|
|
let build = Int(versions.dropFirst().dropFirst().dropFirst().first ?? "0") {
|
|
return Version(node: node,
|
|
timeSpent: timeSpent,
|
|
major: major,
|
|
minor: minor,
|
|
patch: patch,
|
|
build: build)
|
|
}
|
|
return Version.createInvalid(for: node)
|
|
}
|
|
}
|
|
|
|
var childTaskResults = [Version]()
|
|
for await result in taskGroup {
|
|
if !childTaskResults.isEmpty {
|
|
print("Too late for version from \(result.node) some node was faster \(childTaskResults.sorted(by: { $0.timeSpent < $1.timeSpent }).map { $0.node }.joined(separator: ", "))")
|
|
}
|
|
childTaskResults.append(result)
|
|
if result.major < 1000 {
|
|
self.environment.current
|
|
.update(nodes: [result.node] + nodes.filter { $0 != result.node })
|
|
return [result]
|
|
}
|
|
}
|
|
|
|
return childTaskResults
|
|
}
|
|
// let node = results.sorted(by: { $0.1 < $1.1 })
|
|
|
|
if let result = results.first(where: { $0.isValid }) {
|
|
if Bundle.main.major < result.major {
|
|
throw ServiceError.version(cause: L10n.Main.wrongVersion)
|
|
} else if Bundle.main.major == result.major {
|
|
if Bundle.main.minor < result.minor {
|
|
throw ServiceError.version(cause: L10n.Main.wrongVersion)
|
|
} else if Bundle.main.minor == result.minor {
|
|
if Bundle.main.patch < result.patch {
|
|
throw ServiceError.version(cause: L10n.Main.wrongVersion)
|
|
} else if Bundle.main.patch == result.patch {
|
|
if Bundle.main.build < result.build {
|
|
throw ServiceError.version(cause: L10n.Main.wrongVersion)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
throw ServiceError.version(cause: L10n.Main.noVersion)
|
|
}
|
|
}
|
|
|
|
func loadNodes() async throws {
|
|
let record = self.environment.current
|
|
let environment = try record.networkEnvironment()
|
|
let service = EOSService(environment: environment)
|
|
let nodes = await service.requireNodes()
|
|
|
|
if nodes.isEmpty {
|
|
throw ServiceError.nodes(cause: L10n.Main.noNodes)
|
|
}
|
|
self.environment.current.update(nodes: nodes)
|
|
}
|
|
|
|
func loadHyperions() async throws {
|
|
let record = self.environment.current
|
|
let environment = try record.networkEnvironment()
|
|
let service = EOSService(environment: environment)
|
|
let hyperions = await service.requireHyperions()
|
|
|
|
let finalHyperions = hyperions.isEmpty ? Self.defaultHyperions(for: record.backend.name) : hyperions
|
|
|
|
if finalHyperions.isEmpty {
|
|
throw ServiceError.hyperions(cause: L10n.Main.noHyperion)
|
|
}
|
|
|
|
self.environment.current.update(hyperions: finalHyperions)
|
|
}
|
|
|
|
@discardableResult
|
|
func loadAppSettings(nodes: [String]) async -> AppSettingsModel {
|
|
|
|
guard let node = nodes.first else { return AppSettingsModel.shared() }
|
|
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.shared() }
|
|
|
|
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) {
|
|
return AppSettingsModel.shared(with: data)
|
|
}
|
|
|
|
return AppSettingsModel.shared()
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private static func defaultHyperions(for backend: String) -> [String] {
|
|
guard let url = Bundle.main.url(forResource: "NetworkSettings", withExtension: "bundle"),
|
|
let networkBundle = Bundle(url: url),
|
|
let hyperionsDict = networkBundle.infoDictionary?["hyperions"] as? [String: [String]],
|
|
let hyperions = hyperionsDict[backend] else {
|
|
return []
|
|
}
|
|
return hyperions
|
|
}
|
|
|
|
}
|