Files

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
}
}