3 Commits

Author SHA1 Message Date
Juraldinio c564f69b82 WiP 2022-05-30 12:55:13 +03:00
Juraldinio 52919cde7f Remove project files from git 2022-05-30 11:54:41 +03:00
Juraldinio c07fb77ce5 Refactoring huge ApplicationDelegate 2022-05-30 10:19:50 +03:00
2529 changed files with 59158 additions and 425451 deletions
-5
View File
@@ -93,9 +93,4 @@ iOSInjectionProject/
Info.plist
/PayCash.entitlements
/PayCash.xcodeproj
/ResultIPA/
*.pbxproj
*.xcscheme
*.xcodeproj
*.entitlements
/iOS/Assets/Settings.bundle
+18 -34
View File
@@ -1,6 +1,5 @@
# Execute always
before_script:
- echo $CI_PIPELINE_IID
- echo $GITLAB_USER_ID
- echo ${CI_COMMIT_REF_SLUG}
- id
@@ -10,12 +9,13 @@ variables:
stages:
- lint
- test
# - test
- build
- deploy
- notification
# Linting stage
.linting: &linting
tags:
- malinka
@@ -34,25 +34,6 @@ Lint:
- /^feature/
- merge_requests
# Testing stage
.testing: &testing
tags:
- malinka
stage: test
when: always
allow_failure: false
script:
- chmod +x ./toolchain/testing.sh
- ./toolchain/testing.sh
Test:
<<: *testing
only:
- develop
- /^bugfix/
- /^feature/
- merge_requests
# Build IPA
.BuildIPA: &BuildIPA
tags:
@@ -63,20 +44,23 @@ Test:
script:
- rm -rf ./ResultIPA
- chmod +x ./toolchain/build_iOS.sh
- ./toolchain/build_iOS.sh ${APPLICATION_DEPLOY_TYPE} ${APPLICATION_NAME} ${APPLICATION_SCHEME_NAME}
# - ./toolchain/build_iOS.sh PRODUCTION
- ./toolchain/build_iOS.sh BETA
# Build stage
build:
stage: build
<<: *BuildIPA
only:
- tags
only:
- master
- develop
- tags
- merge_requests
artifacts:
paths:
- ./ResultIPA
expire_in: 2 days
expire_in: 3 days
buildLeaf:
stage: build
@@ -101,7 +85,7 @@ buildLeaf:
IPA_PATH: ./ResultIPA
script:
- chmod +x ./toolchain/deploy_iOS.sh
- ./toolchain/deploy_iOS.sh ${IPA_DEPLOY_TARGET} ${IPA_PATH} ${APPLICATION_SCHEME_NAME}
- ./toolchain/deploy_iOS.sh ${IPA_DEPLOY_TARGET} ${IPA_PATH}
needs:
- job: build
artifacts: true
@@ -111,8 +95,9 @@ deploy:
<<: *DeployIPA
when: on_success
only:
- tags
- master
- develop
- tags
needs:
- job: build
artifacts: true
@@ -137,29 +122,28 @@ deployLeaf:
script:
- chmod +x ./toolchain/slack_notification.sh
- ./toolchain/slack_notification.sh ${BUILD_RESULT}
needs:
- job: deploy
# Develop
FailureNotification:
variables:
BUILD_RESULT: "FAILURE"
when: on_failure
only:
- tags
- master
- develop
- tags
<<: *slacknotification
needs:
- job: deploy
SuccessNotification:
variables:
BUILD_RESULT: "SUCCESS"
when: on_success
only:
- tags
- master
- develop
- tags
<<: *slacknotification
needs:
- job: deploy
# Merge checks
-32
View File
@@ -1,32 +0,0 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "WalletFoundation",
platforms: [.iOS(.v13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "WalletFoundation",
targets: ["WalletFoundation"]),
],
dependencies: [
.package(name: "KeyChainAccess", path: "../../Vendors/spm/KeyChainAccess")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "WalletFoundation",
dependencies: ["KeyChainAccess"],
path: "./Sources"),
.testTarget(
name: "WalletFoundationTests",
dependencies: ["WalletFoundation"],
path: "Tests"//, // Test files
// resources: [.copy("TestData")] // The test data files, copy files without modifying them
)
]
)
@@ -1,90 +0,0 @@
//
// ApplicationSettings.swift
//
//
// Created by Juraldinio on 8/28/22.
//
import Foundation
fileprivate enum ApplicationSettingsKeys {
// TODO: - Need remove after 1.4.0 version
static let ignoreDeviceSatusKeyOld = "Settings.device_ignore"
static let ignoreCreateAccountSatusKeyOld = "Status.createAccount_ignore"
static let isNetworkLogEnabledKeyOld = "Settings.network_copy"
static let isDeviceTokenCopyEnabledKeyOld = "Settings.device_token_copy_enable"
static let apiEnvironmentKeyOld = "Settings.api_environment"
static let apiPaycashEnvironmentKeyOld = "Settings.api_paycash_environment"
static let deviceDescriptionKeyOld = "Settings.device_id"
static let deviceTokenKeyOld = "Settings.device_token"
/// Ignore device status on register flow
static let ignoreDeviceSatusKey = "Settings.application.device_ignore"
/// Ignore create account status on register flow
static let ignoreCreateAccountSatusKey = "Status.application.createAccount_ignore"
/// log all network activity
static let isNetworkLogEnabledKey = "Settings.application.network_copy"
/// Copy device token to Settings fields
static let isDeviceTokenCopyEnabledKey = "Settings.application.device_token_copy_enable"
/// Field in Settings for device description
static let deviceDescriptionKey = "Settings.application.device_id"
/// Field in Settings for device token
static let deviceTokenKey = "Settings.application.device_token"
static let apiUsernameEnvironmentKey = "Settings.application.environment.usernames"
static let apiBackendEnvironmentKey = "Settings.application.environment.backend"
static let otherEnvironmentKey = "Settings.application.environment.other"
static let smartEnvironmentKey = "Settings.application.environment.smart"
/// Field in Settings for Firebase token lifetime
static let firebaseTokenLifetimeKey = "Settings.application.firebase.token.lifetime"
}
public enum ApplicationSettings {
public static var ignoreDeviceSatus: Bool { UserDefaults.standard.bool(forKey: ApplicationSettingsKeys.ignoreDeviceSatusKey) }
public static var ignoreCreateAccountSatus: Bool { UserDefaults.standard.bool(forKey: ApplicationSettingsKeys.ignoreCreateAccountSatusKey) }
public static var isNetworkLogEnabled: Bool { UserDefaults.standard.bool(forKey: ApplicationSettingsKeys.isNetworkLogEnabledKey) }
public static var isDeviceTokenCopyEnabled: Bool { UserDefaults.standard.bool(forKey: ApplicationSettingsKeys.isDeviceTokenCopyEnabledKey) }
public static func device(description: String) { UserDefaults.standard.set(description, forKey: ApplicationSettingsKeys.deviceDescriptionKey) }
public static func device(token: String) { UserDefaults.standard.set(token, forKey: ApplicationSettingsKeys.deviceTokenKey) }
public static var apiUsernameEnvironment: String? { UserDefaults.standard.string(forKey: ApplicationSettingsKeys.apiUsernameEnvironmentKey) }
public static var apiBackendEnvironment: String? { UserDefaults.standard.string(forKey: ApplicationSettingsKeys.apiBackendEnvironmentKey) }
public static var otherEnvironment: String? { UserDefaults.standard.string(forKey: ApplicationSettingsKeys.otherEnvironmentKey) }
public static var smartsEnvironment: String? { UserDefaults.standard.string(forKey: ApplicationSettingsKeys.smartEnvironmentKey) }
public static var firebaseTokenLifetime: Int? { UserDefaults.standard.integer(forKey: ApplicationSettingsKeys.firebaseTokenLifetimeKey) }
public static func clearApiEnvironment() {
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.ignoreDeviceSatusKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.ignoreCreateAccountSatusKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.isNetworkLogEnabledKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.isDeviceTokenCopyEnabledKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.deviceDescriptionKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.deviceTokenKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.apiUsernameEnvironmentKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.apiBackendEnvironmentKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.otherEnvironmentKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.smartEnvironmentKey)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.firebaseTokenLifetimeKey)
// TODO: - Need remove after 1.4.0 version
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.ignoreDeviceSatusKeyOld)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.ignoreCreateAccountSatusKeyOld)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.isNetworkLogEnabledKeyOld)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.isDeviceTokenCopyEnabledKeyOld)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.apiEnvironmentKeyOld)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.apiPaycashEnvironmentKeyOld)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.deviceDescriptionKeyOld)
UserDefaults.standard.removeObject(forKey: ApplicationSettingsKeys.deviceTokenKeyOld)
}
}
@@ -1,26 +0,0 @@
//
// CommonKey.swift
//
//
// Created by Juraldinio on 11/25/22.
//
import Foundation
public struct CommonKey: RawRepresentable, Hashable {
public var rawValue: String
public init?(rawValue: String) {
self.rawValue = rawValue
}
public init(_ rawValue: String) {
self.rawValue = rawValue
}
public func with(_ suffix: String) -> Self { .init(rawValue + "." + suffix) }
public static func key(_ key: String) -> Self { .init(key) }
public static func key(_ key: String, suffix: String) -> Self { Self.key(key).with(suffix) }
}
@@ -1,26 +0,0 @@
//
// KeychainProtocol.swift
//
//
// Created by Nut.Tech on 16.02.2023.
//
import Foundation
public typealias Key = CommonKey
public protocol KeychainProtocol {
/// Checks if a given common key have a password-saved value
/// - Parameter key: Common Key
/// - Returns: is value exists
func exist(_ key: Key) -> Bool
/// Checks if a given common key have a biometric-saved value
/// - Parameter key: Common Key
/// - Returns: is value exists
func bioExist(_ key: Key) -> Bool
/// Access to common key value by biometric authenfication
subscript(biometric key: Key) -> String? { get }
/// Access to common key value by password authenfication
subscript(_ key: Key, password password: String) -> String? { get set }
}
@@ -1,189 +0,0 @@
//
// WalletKeychain.swift
//
//
// Created by Juraldinio on 11/27/22.
//
import LocalAuthentication
final public class WalletKeychain: KeychainProtocol {
private enum KeychainLocals {
public static let password = "PWD"
public static let biometric = "BIO"
}
public static let instance = WalletKeychain()
// MARK: - Init
private init() { }
// MARK: - Interface
public func exist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(KeychainLocals.password)) }
public func bioExist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(KeychainLocals.biometric) ) }
public subscript(biometric key: Key) -> String? {
self.loadBiometricProtected(key: key.with(KeychainLocals.biometric))
.map({ String(data: $0, encoding: .utf8) }) ?? nil
}
public subscript(_ key: Key, password password: String) -> String? {
get { loadPassProtected(key: key.with(KeychainLocals.password), password: password).map { String(data: $0, encoding: .utf8) } ?? nil }
set { update(key, password: password, newValue: newValue) }
}
// MARK: - Private
private func getPwdSecAccessControl() -> SecAccessControl {
var access: SecAccessControl?
var error: Unmanaged<CFError>?
access = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .applicationPassword, &error)
precondition(access != nil, "SecAccessControlCreateWithFlags failed")
return access! // swiftlint:disable:this force_unwrapping
}
@discardableResult
private func setPassProtected(key: Key, data: String, password: String) -> Bool {
let context = LAContext()
context.setCredential(password.data(using: .utf8), type: .applicationPassword)
let query = [
kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: key.rawValue,
kSecAttrAccessControl as String: getPwdSecAccessControl(),
kSecValueData as String: (data.data(using: .utf8) ?? Data()) as NSData,
kSecUseAuthenticationContext: context
] as CFDictionary
let status: OSStatus = SecItemAdd(query, nil)
if status == errSecSuccess {
return true
} else if status == errSecDuplicateItem {
if removeProtected(key: key) {
return setPassProtected(key: key, data: data, password: password)
} else {
return false
}
} else {
return false
}
}
private func loadPassProtected(key: Key, password: String) -> Data? {
let context = LAContext()
context.setCredential(password.data(using: .utf8), type: .applicationPassword)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key.rawValue,
kSecReturnData as String: kCFBooleanTrue!,
kSecAttrAccessControl as String: getPwdSecAccessControl(),
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseAuthenticationContext as String: context,
kSecUseAuthenticationUI as String: kSecUseAuthenticationUIFail
]
var dataTypeRef: AnyObject?
let result = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if result == noErr,
let value = dataTypeRef as? Data {
return value
}
return nil
}
// MARK: - Biometric entries
private func getBiometricSecAccessControl() -> SecAccessControl {
var access: SecAccessControl?
var error: Unmanaged<CFError>?
access = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .biometryCurrentSet, &error)
precondition(access != nil, "SecAccessControlCreateWithFlags failed")
return access! // swiftlint:disable:this force_unwrapping
}
@discardableResult
private func setBiometricEntry(key: Key, data: String) -> Bool {
let query = [
kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: key.rawValue,
kSecAttrAccessControl as String: getBiometricSecAccessControl(),
kSecValueData as String: (data.data(using: .utf8) ?? Data()) as NSData,
] as CFDictionary
let status: OSStatus = SecItemAdd(query, nil)
if status == errSecSuccess {
return true
} else if status == errSecDuplicateItem {
if removeProtected(key: key) {
return setBiometricEntry(key: key, data: data)
} else {
return false
}
} else {
return false
}
}
@discardableResult
private func loadBiometricProtected(key: Key) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key.rawValue,
kSecReturnData as String: kCFBooleanTrue as Any,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseOperationPrompt as String: "Access your data"
]
var dataTypeRef: AnyObject?
return SecItemCopyMatching(query as CFDictionary, &dataTypeRef) == noErr ? dataTypeRef as? Data : nil
}
// MARK: -
private func update(_ key: Key, password: String, newValue: String?) {
//TODO: Refactor later - updation of biometric entry only when it is available and needed
if let value = newValue {
setPassProtected(key: key.with(KeychainLocals.password), data: value, password: password)
setBiometricEntry(key: key.with(KeychainLocals.biometric), data: value)
} else {
removeProtected(key: key.with(KeychainLocals.password))
removeProtected(key: key.with(KeychainLocals.biometric))
}
}
@discardableResult
private func removeProtected(key: Key) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key.rawValue
]
return SecItemDelete(query as CFDictionary) == noErr
}
private func checkProtectedExist(key: Key) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key.rawValue,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseAuthenticationUI as String: kSecUseAuthenticationUIFail
]
let status = SecItemCopyMatching(query as CFDictionary, nil)
switch status {
case errSecSuccess, errSecInteractionNotAllowed: return true
case errSecItemNotFound: return false
default: return false
}
}
}
@@ -1,54 +0,0 @@
//
// Settings.swift
//
//
// Created by Juraldinio on 11/29/22.
//
import Foundation
final public class Settings {
public static let shared = Settings()
private init() {}
public func contains(_ key: CommonKey) -> Bool { defaults.value(forKey: key.rawValue) != nil }
public subscript<T>(_ key: CommonKey) -> T? {
get { defaults.value(forKey: key.rawValue) as? T }
set { set(value: newValue, for: key) }
}
public subscript<T: Codable>(_ key: CommonKey) -> [T]? {
get {
let decoder = JSONDecoder()
guard let data = defaults.value(forKey: key.rawValue) as? Data,
let value = try? decoder.decode([T].self, from: data) else { return nil }
return value
}
set {
let encoder = JSONEncoder()
guard let value = newValue,
let data = try? encoder.encode(value) else { return }
set(value: data, for: key)
}
}
public subscript(_ key: CommonKey) -> Bool {
get { defaults.bool(forKey: key.rawValue) }
set { set(value: newValue, for: key) }
}
public subscript(_ key: CommonKey) -> Int {
get { (defaults.value(forKey: key.rawValue) as? Int) ?? 0 }
set { set(value: newValue, for: key) }
}
private func set(value: Any?, for key: CommonKey) {
defaults.setValue(value, forKey: key.rawValue)
defaults.synchronize()
}
private var defaults: UserDefaults { UserDefaults.standard }
}
@@ -1,55 +0,0 @@
//
// DeviceToken.swift
//
//
// Created by NUT.Tech on 02.08.2022.
//
import Foundation
import DeviceCheck
import UIKit
public enum DeviceCheckTokenError: Error {
case notSupported
case generation
}
public struct DeviceCheckToken {
public let token: String
public let isSimulator: Bool
public static func generate() async -> Result<DeviceCheckToken, DeviceCheckTokenError> {
let token: String
let isSimulator: Bool
#if targetEnvironment(simulator)
isSimulator = true
token = tokenConstant
#else
let device = DCDevice.current
guard device.isSupported else { return .failure(.notSupported) }
guard let data = try? await device.generateToken() else { return .failure(.generation) }
isSimulator = false
token = data.base64EncodedString()
#endif
if ApplicationSettings.isDeviceTokenCopyEnabled {
ApplicationSettings.device(token: token)
}
return .success(DeviceCheckToken(token: token, isSimulator: isSimulator))
}
}
#if targetEnvironment(simulator)
fileprivate let tokenConstant = """
AgAAAIArgRs5gVfPAEHSra0ejd0EUNk0+me89vLfv5ZingpyOOkgXXXyjPzYTzWmWSu+BYqcD47byirLZ++3dJccpF99hWppT7G5xAuU+y56WpSYsATWySfuxbSSMT9JSoOWz4QtiDmmVmUHbzCfHbTP3Tr3hsG+86KBBaoSqHtDRy+dtKlV32kDRbuuniBy3nseZsZoCggAAKTXDFKWHDZ55Ya1Cp+8s+dGOyi4C08v0P/8rbQHcjjkOphpLUqKaBZCykAaf5Ue1c1ul57OKeoyaDy9ShXGvwIKIcZrvZBBds3wwEFQuZBNPTG1ZvpIZ3npXscHWaKRd228V/bboEKarukYi3+lsxafZj+R8laD0Ex3nP3WZaIU4990oerdiu6wbdELlaNyrVF6SRJcZQhhflfnnyXHMqw6c41Hk3toMHsUd9wzdzcYmZB1PI5rsgSfnWrxAbBy5rYpH+ZkGkhcHFJqQIO4TB8ZKU0KSjmS8mBnVDT/1VPgiNDz/qI+KWiZ1xcuEwwvaEmD+Fk6Pt9GMRsfxI+SvldTgk8REqr9dBXt69xwM2FHBHP9k4okkaMbsU1qpZPKCwsnTBrkbvLn0zVn2+tfTukLx0O+uCPRMluXn3EXDrQ0aRHFbUtgoMqOx0JP+7tj/BaOAKkc3C2GaCRfEes2YmqZkM4pMve4Qh1jAsIwYtGcDz7AeGQ61kVrc4CFj3xj2OgC+IbBi7naOZqNvr1Be1YPKt8Vpig9YF5GueY9p4DRGlOG6UX2RY4wsZTt38Lxw5uBBkWuINjRyqsiN5obPZ3xLagfzfNsFDBYgBsGrib8nURVfSgAqIgrozOl+cppRB7xoN9QRti+HAJqKNd6sqpsPkXzqnDWPBD2Jdg2WCJE2bjJhTqyJ6L3lHFguOdIPc2P6F1CGx4bn1GtegOLlFxexOjMzfU86gJOhYjkVGHt8GD96ohRl75/fsv1reCI16pWt2x8p+Bbh4kko8wP3FtXiun+i6gPDMBhE30Ye5ATsIUIHFZjHOA8UfntaEyCSAngQebQ0X4UERcue+GKY4RqVfPhuVqJa3RHt35Ci/2g7VWllNs1NKYsPofAgTNO2n/kwGrnnIL+gQPNsO6XjnqDOjjT/eDfZX88eK4k6+8+MnW3+l8IAZOIh5JT++JZvBadrNZPV7G/2ME6G1FCIAZ4icCidbMzpj4sGc8dlJOg/B3457/Vt8CHLulhajQIsXQuqGDotgzirELTVQ+Z2eh3a8W/Pu+g8iNIUL1MCEzHg7RefM6tUescNDH6uPEuEeXKmrsoVbHdUvhuLIVbQGHMCjuGbmxmczcfIcRAPWkjwuVQOKBpoaD/Zep2gM71inpz6056bmvUuTMM9MV6sM87Fqrv6TvIRT/ch/i7Flmv56ERn/NGryafdcvDIu+JMs5U3yvb3STTSZmbh8RXVGIJjOZ1FJYLREUxxK7eEGM2JLCD+CxR7LuLRuN4AtpI4GIhFrbdVdDkywqNpvEY6aGEOFnD9NP6neBuHRhK/AzqpDE83uFf+1JiPPY7aHYVoQhCxkPs8ex0qJnjHaveKiWfSmaZ6JfY/vVByzJNr5XD6ZSQlJQJ3+xjRb+blTR0XcZ5BHI9ovQQAmGQljWpGPnD5CZQ7ah5kVoK1SbPqtxY6J5zQUTjtTpSe3l6By/nopXH6HSQXJGotOzb+eMOOHFhDC9ypq3urHY+Q1jXB18eR/xkXEIlZQsPBmwCLhoNltD57faLzlqgiwinHjqslntnvfsMkoIpQnWFwLYKh0biW9KM5ZWv7CMIxcc+Wjl1FVnyUrMzhz/IIW6WshcNmZWFSMpaKzozxIyFQZ/IjZHPrE1SI33PwGn/Tro8ZRSf9mpKJbA3uidrygVz6WQlJJwR3ujdgZ9aJ9WZQWVehVYj5mMg5P5MB/BG1G/TQNTLn72Root26hSB+8WMCAo/EEY3L5Qox+JabfDV+kBsHjFtDfyo7ghp/AOvkVzudzzk+F3ruc1bIDJlSfOQ5WRTkjmQGhKDIfDvLIi4Mt8bQTzZ3KPkLaU+hwuFE7m9c/MgzmLWK24oDsZ5nZ4oBeUNc+lThUuz7qnWENE1sIXij3zFljsoH+HOPb+zSt8m7iwszxMZ6T84LvqxqWVGEkI4Il2s41ti5QH74nOzTaci+5dYRVWOnY/hAwI51HE1sb3Pe+NsSHcfgtDW0E4Xx+Wx0uLdqbTlriXRnUIOoi9PVNR6XOdJ4nDOxyiOMMhnUooQ7lRqm0hCxw9nAEw5+PvYWWFXxPaupUfeVsjH+9dXW6bzosCGzTbVpHcDjPWie70r+Nma+oOwA4ARKHmGsbKcoO43xos6sqfbZTCCP9BIPQnZ8XbUen9G7eMs9ESigKoynKKVGNmsBXK4lU8xM/qLXXudViMdSPOZ6mghjJCNK0yA1v9l/ipZRHiTPFOttELH6Ip+fKDtfqdeEqCiPrSnVtWzehUKOUhlNJtexkOZcb2Dtq4L7JlJ0GkJg80vCvEYvArM2JpPqKDVr8hCBNC87u6zk9T3E+L2dfL30aiNVAGTl44Qw0pPerIr1a6m9Jkj690Pi3OI7UAgWaoQjxYm2my7DZMqtkL6CrT0NW9KnihXw701ngJysdKcZ0JkMDT2LzP+2Nj10WIOwkLxASexSQgSoyGk7yTYLUAyvwN1rhRtspXaiyOcfyzDwgTIU9Sn/jMbC6fv7GPReCsiFR8Xa6VCj37eFPXgBiOpAYtj/zMz/3S/io3LTqs7QG1M14CX31xSxu21tASOzaRhbd2RB2QCHXgpqv4593psE5EPjbRZt5DN2toQ4XJJ1A1/EcyDkEJ8+1gu34aVrqC6ejm/07/MQ7ISmUuPrJyCaPIW+PbkxF0VpYU5lJ9HP+LD7WggPwi8NVz8zFWNtyTM6aUuTNL69sBHpWlYeqCwpkJ+EcJFuaTnT27N4pFvwA==
"""
#endif
@@ -1,346 +0,0 @@
//
// DeviceUUID.swift
//
//
// Created Nut.Tech on 02.08.2022.
//
#if os(iOS)
import UIKit
#elseif os(macOS)
import AppKit
#endif
import KeyChainAccess
/// Class allow retrive UUID with different life cycle.
final public class DeviceUUID {
private enum Constants {
enum Keys {
static let installationUUIDKey = "installationUUIDKey"
static let deviceUUIDKey = "deviceUUIDKey"
static let devicesUUIDsKey = "devicesUUIDsKey"
static let devicesUUIDsToggleKey = "devicesUUIDsToggleKey"
static let devicesUUIDs = "devicesUUIDs"
}
enum Locals {
static let DevicesUUIDsDidChangeNotification = NSNotification.Name(rawValue: "DevicesUUIDsDidChangeNotification")
}
}
/// Shared instance for class
static public var shared = DeviceUUID()
/// Changes each time the app gets launched (persistent to session).
lazy private(set) public var session: String = {
self.generateUuid()
}()
/// Changes each time the app gets installed (persistent to installation).
lazy private(set) public var installation: String = {
self.getValue(forKey: Constants.Keys.installationUUIDKey,
defaultValue: nil,
keychain: false,
synchronizable: false)
}()
/// Changes each time all the apps of the same vendor are uninstalled (this works exactly as identifierForVendor).
lazy private(set) public var vendor: String? = {
#if os(iOS)
UIDevice.current
.identifierForVendor?
.uuidString
.lowercased()
.replacingOccurrences(of: "-", with: "")
#elseif os(macOS)
return nil
#endif
}()
/// Changes only on system reset, this is the best replacement to the good old udid (persistent to device)
lazy private(set) public var device: String = {
self.getValue(forKey: Constants.Keys.deviceUUIDKey,
defaultValue: nil,
synchronizable: false)
}()
/// List of all uuidForDevice of the same user.
/// In this way it's possible manage guest accounts across multiple devices easily
lazy private(set) public var devices: [String] = {
let devicesString = self.getValue(forKey: Constants.Keys.devicesUUIDsKey,
defaultValue: self.device)
return devicesString.components(separatedBy: "|")
}()
/// Changes each time (no persistent), but allows to keep in memory more temporary uuids.
public func uuid(forKey key: String) -> String {
guard let uuid = self.uuids[key] else {
let value = self.generateUuid()
self.uuids[key] = value
return value
}
return uuid
}
/// Changes each time (no persistent)
public func generateUuid() -> String {
let uuidRef = CFUUIDCreate(nil)
let uuidStringRef = CFUUIDCreateString(nil, uuidRef)
return ((uuidStringRef as? String) ?? "")
.lowercased()
.replacingOccurrences(of: "-", with: "")
}
// MARK: - Private
private var uuids = [String: String]()
private var isCloudAvailable = false
// MARK: - Init
init() {
self.initCloudUUIDsDevices()
}
// MARK: - Internal
func getValue(forKey key: String,
defaultValue: String? = nil,
userDefaults: Bool = true,
keychain: Bool = true,
service: String? = nil,
accessGroup: String? = nil,
synchronizable: Bool = true) -> String {
if let newValue = Self.getValue(forKey: key,
userDefaults:
userDefaults,
keychain: keychain,
service: service,
accessGroup: accessGroup) {
return newValue
} else {
let value = defaultValue ?? self.generateUuid()
Self.setValue(value,
forKey: key,
userDefaults: userDefaults,
keychain: keychain,
service: service,
accessGroup: accessGroup,
synchronizable: synchronizable)
return value
}
}
public func uuidForDeviceMigratingValue(forKey key: String,
service: String? = nil,
accessGroup: String? = nil,
commitMigration: Bool) -> String? {
if let uuidToMigrate = Self.getValue(forKey: key,
service: service,
accessGroup: accessGroup) {
return self.uuid(forDeviceMigratingValue: uuidToMigrate, commitMigration: commitMigration)
}
return nil
}
func updateUUIDDevice(value: String) {
self.device = value
Self.setValue(value,
forKey: Constants.Keys.deviceUUIDKey,
synchronizable: false)
}
func uuid(forDeviceMigratingValue value: String, commitMigration: Bool) -> String? {
if self.isValidUUID(value) {
let oldValue = self.device
let newValue = value
guard oldValue != newValue else { return oldValue }
if commitMigration {
self.updateUUIDDevice(value: newValue)
let deviceSet = NSMutableOrderedSet(array: self.devices)
deviceSet.add(newValue)
deviceSet.remove(oldValue)
if let uuidsArray = deviceSet.array as? [String] {
self.updateUUIDsDevices(with: uuidsArray)
}
self.syncCloudUUIDsDevices()
return self.device
} else {
return oldValue
}
} else {
let exception = NSException(name: NSExceptionName(rawValue: "Invalid uuid to migrate"),
reason: "uuid value should be a string of 32 or 36 characters.")
exception.raise()
return nil
}
}
private func initCloudUUIDsDevices() {
self.isCloudAvailable = false
guard FileManager.default.ubiquityIdentityToken != nil else { return }
self.isCloudAvailable = true
NotificationCenter.default.addObserver(self,
selector: #selector(self.changesCloudUUIDsDevicesNotification),
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: nil)
self.syncCloudUUIDsDevices()
}
private func syncCloudUUIDsDevices() {
if self.isCloudAvailable {
let iCloud = NSUbiquitousKeyValueStore.default
//if keychain contains more device identifiers than icloud, maybe that icloud has been empty, so re-write these identifiers to iCloud
for uuidOfUserDevice in self.devices {
let uuidOfUserDeviceAsKey = "\(Constants.Keys.deviceUUIDKey)_\(uuidOfUserDevice)"
if iCloud.string(forKey: uuidOfUserDeviceAsKey) != uuidOfUserDevice {
iCloud.set(uuidOfUserDevice, forKey: uuidOfUserDeviceAsKey)
}
}
//toggle a boolean value to force notification on other devices, useful for debug
let uuidsOfUserDevicesToggler = !iCloud.bool(forKey: Constants.Keys.devicesUUIDsToggleKey)
iCloud.set(uuidsOfUserDevicesToggler, forKey: Constants.Keys.devicesUUIDsToggleKey)
iCloud.synchronize()
}
}
private func updateUUIDsDevices(with value: [String]) {
self.devices = value
Self.setValue(value.joined(separator: "|"),
forKey: Constants.Keys.devicesUUIDsKey)
}
private func isValidUUID(_ value: String) -> Bool {
let pattern = "^[0-9a-f]{32}|[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$"
guard let regExp = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) else {
return false
}
let uuidValueRange = NSRange(location: 0, length: value.count)
let matchRange = regExp.rangeOfFirstMatch(in: value, options: [], range: uuidValueRange)
var matchValue: String?
if !NSEqualRanges(matchRange, NSRange(location: NSNotFound, length: 0)) {
matchValue = (value as NSString).substring(with: matchRange)
return matchValue == value ? true : false
} else {
return false
}
}
@objc
private func changesCloudUUIDsDevicesNotification(_ notification: Notification?) {
if self.isCloudAvailable {
let uuidsSet = NSMutableOrderedSet(array: self.devices)
let uuidsCount = uuidsSet.count
let iCloud = NSUbiquitousKeyValueStore.default
let iCloudDict = iCloud.dictionaryRepresentation as NSDictionary
iCloudDict.enumerateKeysAndObjects { key, obj, stop in
let uuidKey = key as? NSString
if uuidKey?.range(of: Constants.Keys.deviceUUIDKey).location == 0 {
if let uuidValue = obj as? String {
if uuidKey?.range(of: uuidValue).location != NSNotFound,
self.isValidUUID(uuidValue) {
uuidsSet.add(uuidValue)
} else {
print("invalid uuid")
}
}
}
}
if uuidsSet.count > uuidsCount,
let uuidsArray = uuidsSet.array as? [String] {
self.updateUUIDsDevices(with: uuidsArray)
let userInfo = [Constants.Keys.devicesUUIDs: self.devices]
NotificationCenter.default.post(name: Constants.Locals.DevicesUUIDsDidChangeNotification,
object: self,
userInfo: userInfo)
}
}
}
// MARK: - Static
static private func getValue(forKey key: String,
userDefaults: Bool = true,
keychain: Bool = true,
service: String? = nil,
accessGroup: String? = nil) -> String? {
let keychainStore = Self.makeKeychain(service: service, accessGroup: accessGroup)
var value = try? keychainStore.getString(key)
if userDefaults,
!value.isExist {
value = UserDefaults.standard.string(forKey: key)
}
return value
}
@discardableResult
static private func setValue(_ value: String?,
forKey key: String,
userDefaults: Bool = true,
keychain: Bool = true,
service: String? = nil,
accessGroup: String? = nil,
synchronizable: Bool = true) -> Error? {
if let value = value,
userDefaults {
UserDefaults.standard.set(value, forKey: key)
UserDefaults.standard.synchronize()
}
if let value = value, keychain {
let keychainStore = Self.makeKeychain(service: service, accessGroup: accessGroup).synchronizable(synchronizable)
do {
try keychainStore.set(value, key: key)
} catch {
return error
}
}
return nil
}
static private func makeKeychain(service: String? = nil, accessGroup: String? = nil) -> Keychain {
if let service = service {
if let accessGroup = accessGroup {
return Keychain(service: service, accessGroup: accessGroup)
} else {
return Keychain(service: service)
}
} else if let accessGroup = accessGroup {
return Keychain(accessGroup: accessGroup)
}
return Keychain()
}
}
@@ -1,23 +0,0 @@
//
// Array+Extension.swift
//
//
// Created by Juraldinio on 9/15/22.
//
import Foundation
public extension Array {
subscript(safe index: Index) -> Element? {
return (self.startIndex..<self.endIndex) ~= index ? self[index] : nil
}
}
public extension Array where Element: Hashable {
func distinct() -> Array {
let set = Set(self)
return Array(set)
}
}
@@ -1,25 +0,0 @@
//
// Data+Extension.swift
//
//
// Created by Juraldinio on 12/6/22.
//
import Foundation
public extension Data {
func jsonDecoded<T: Decodable>(type: T.Type, userInfo: [CodingUserInfoKey: Any]? = nil) -> T? {
try? self.makeDecoder(userInfo: userInfo).decode(type, from: self)
}
func jsonDecoded<T: Decodable>(type: T.Type, userInfo: [CodingUserInfoKey: Any]? = nil) -> [T]? {
try? self.makeDecoder(userInfo: userInfo).decode([T].self, from: self)
}
private func makeDecoder(userInfo: [CodingUserInfoKey: Any]?) -> JSONDecoder {
let jsonDecoder = JSONDecoder()
userInfo >>- { jsonDecoder.userInfo = $0 }
return jsonDecoder
}
}
@@ -1,15 +0,0 @@
//
// Dictionary+Extension.swift
//
//
// Created by NUT.Tech on 27.01.2023.
//
import Foundation
public extension Dictionary where Key == String, Value == Any {
func jsonSerialized(options: JSONSerialization.WritingOptions = []) -> Data? {
try? JSONSerialization.data(withJSONObject: self, options: options)
}
}
@@ -1,16 +0,0 @@
//
// Encodable+Extension.swift
//
//
// Created by Juraldinio on 12/6/22.
//
import Foundation
public extension Encodable {
func jsonData() -> Data? {
let encoder = JSONEncoder()
return try? encoder.encode(self)
}
}
@@ -1,32 +0,0 @@
//
// Optional+Extension.swift
//
//
// Created by Juraldinio on 08.08.2022.
//
import Foundation
public extension Optional {
var isExist: Bool {
if case .some = self {
return true
}
return false
}
func orCreate(_ creation: @autoclosure () -> Wrapped) -> Wrapped {
switch self {
case let .some(value): return value
case .none: return creation()
}
}
func orTypedCreate<Element: RawRepresentable>(_ creation: @autoclosure () -> Element) -> Element where Element.RawValue == Wrapped {
switch self {
case let .some(value): return Element(rawValue: value) ?? creation()
case .none: return creation()
}
}
}
@@ -1,43 +0,0 @@
//
// Publisher+Extension.swift
//
//
// Created by Juraldinio on 12/20/22.
//
import Foundation
import Combine
public extension Publisher {
/// Includes the current element as well as the previous element from the upstream publisher in a tuple where the previous element is optional.
/// The first time the upstream publisher emits an element, the previous element will be `nil`.
///
/// let range = (1...5)
/// cancellable = range.publisher
/// .withPrevious()
/// .sink { print ("(\($0.previous), \($0.current))", terminator: " ") }
/// // Prints: "(nil, 1) (Optional(1), 2) (Optional(2), 3) (Optional(3), 4) (Optional(4), 5) ".
///
/// - Returns: A publisher of a tuple of the previous and current elements from the upstream publisher.
func withPrevious() -> AnyPublisher<(previous: Output?, current: Output), Failure> {
scan(Optional<(Output?, Output)>.none) { ($0?.1, $1) }
.compactMap { $0 }
.eraseToAnyPublisher()
}
/// Includes the current element as well as the previous element from the upstream publisher in a tuple where the previous element is not optional.
/// The first time the upstream publisher emits an element, the previous element will be the `initialPreviousValue`.
///
/// let range = (1...5)
/// cancellable = range.publisher
/// .withPrevious(0)
/// .sink { print ("(\($0.previous), \($0.current))", terminator: " ") }
/// // Prints: "(0, 1) (1, 2) (2, 3) (3, 4) (4, 5) ".
///
/// - Parameter initialPreviousValue: The initial value to use as the "previous" value when the upstream publisher emits for the first time.
/// - Returns: A publisher of a tuple of the previous and current elements from the upstream publisher.
func withPrevious(_ initialPreviousValue: Output) -> AnyPublisher<(previous: Output, current: Output), Failure> {
scan((initialPreviousValue, initialPreviousValue)) { ($0.1, $1) }.eraseToAnyPublisher()
}
}
@@ -1,24 +0,0 @@
//
// File.swift
//
//
// Created by Juraldinio on 01.08.2022.
//
import Foundation
public extension String {
func trimCompact() -> String {
let value = self
.replacingOccurrences(of: "\n", with: "")
.replacingOccurrences(of: "\r", with: "")
let regex = try! NSRegularExpression(pattern: "[ ]{2,}", options: .caseInsensitive)
return regex.stringByReplacingMatches(in: value,
options: [],
range: NSRange(0..<value.utf16.count),
withTemplate: " ")
}
}
@@ -1,43 +0,0 @@
//
// Either.swift
//
//
// Created by Juraldinio on 01.08.2022.
//
import Foundation
public enum Either<T: Decodable, U: Decodable>: Decodable {
case firstType(T)
case secondType(U)
public func unwrap() -> Any {
switch self {
case .firstType(let objectOfTypeT): return objectOfTypeT
case .secondType(let objectOfTypeU): return objectOfTypeU
}
}
public func map<V>(firstTypeTransform: (T) -> V, secondTypeTransform: (U) -> V) -> V {
switch self {
case .firstType(let value):
return firstTypeTransform(value)
case .secondType(let value):
return secondTypeTransform(value)
}
}
public init(from decoder: Decoder) throws {
if let value = try? T(from: decoder) {
self = .firstType(value)
} else if let value = try? U(from: decoder) {
self = .secondType(value)
} else {
let context = DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription:
"Cannot decode \(T.self) or \(U.self)")
throw DecodingError.dataCorrupted(context)
}
}
}
@@ -1,53 +0,0 @@
//
// Operations.swift
// Jura
//
// Created by Jura on 8/14/19.
// Copyright © 2019 Jura. All rights reserved.
//
import Foundation
precedencegroup MonadicPrecedence {
associativity: left
higherThan: BitwiseShiftPrecedence
}
infix operator >>- : MonadicPrecedence
@inline(__always)
@discardableResult
public func >>-<T, U>(a: T?, f: (T) throws -> U?) rethrows -> U? {
switch a {
case .some(let x):
return try f(x)
case .none:
return nil
}
}
// MARK: <<< / >>>
precedencegroup FunctionApplicationPrecedenceLeft {
lowerThan: AssignmentPrecedence
associativity: left
}
infix operator >>> : FunctionApplicationPrecedenceLeft
@inline(__always)
public func >>><T, U>(x: T, f: (T) throws -> U) rethrows -> U {
return try f(x)
}
precedencegroup FunctionApplicationPrecedenceRight {
lowerThan: AssignmentPrecedence
associativity: right
}
infix operator <<< : FunctionApplicationPrecedenceRight
@inline(__always)
public func <<<<T, U>(f: (T) throws -> U, x: T) rethrows -> U {
return try f(x)
}
@@ -1,44 +0,0 @@
//
// ArrayTests.swift
//
//
// Created by Juraldinio on 31.10.2022.
//
import Foundation
import XCTest
@testable
import WalletFoundation
final class ArrayTests: XCTestCase {
func testOuter() {
let test = [0, 1, 2]
XCTAssertNil(test[safe: 5])
XCTAssertNil(test[safe: -1])
}
func testInner() {
let test = [1, 3, 5]
var value = test[safe: 1]
XCTAssertNotNil(value)
XCTAssertEqual(value!, 3)
value = test[safe: 2]
XCTAssertNotNil(value)
XCTAssertEqual(value!, 5)
value = test[safe: 0]
XCTAssertNotNil(value)
XCTAssertEqual(value!, 1)
}
func testDistinct() {
let array = [1, 2, 5, 6, 5, 8, 9, 1, 2, 2, 5]
let arrayWithUniqueElements = array.distinct()
XCTAssertEqual(arrayWithUniqueElements.sorted(), [1, 2, 5, 6, 8, 9])
}
}
@@ -1,58 +0,0 @@
//
// OptionalsTests.swift
//
//
// Created by Juraldinio on 11/8/22.
//
import Foundation
import XCTest
@testable
import WalletFoundation
final class OptionalsTests: XCTestCase {
func testIsExists() {
var value: Int?
XCTAssertFalse(value.isExist)
value = 42
XCTAssertTrue(value.isExist)
}
func testOrCreate() {
var value: String?
XCTAssertEqual(value.orCreate("Hello"), "Hello")
value = "world"
XCTAssertEqual(value.orCreate("Hello"), "world")
}
func testOrTypedCreate() {
enum TestCases: String {
case first
case second
}
var value: String?
// Create new value because nil
XCTAssertEqual(value.orTypedCreate(TestCases.first), .first)
XCTAssertNotEqual(value.orTypedCreate(TestCases.first), .second)
value = "Hello"
// Create new value because not matched
XCTAssertEqual(value.orTypedCreate(TestCases.second), .second)
XCTAssertNotEqual(value.orTypedCreate(TestCases.second), .first)
value = "first"
// Not create new value and just match
XCTAssertEqual(value.orTypedCreate(TestCases.second), .first)
XCTAssertNotEqual(value.orTypedCreate(TestCases.second), .second)
}
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
-36
View File
@@ -1,36 +0,0 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "WalletKit",
platforms: [.iOS(.v13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "WalletKit",
targets: ["WalletKit"]),
],
dependencies: [
.package(name: "eosswift", path: "../../Vendors/spm/eos-swift"),
.package(name: "KeyChainAccess", path: "../../Vendors/spm/KeyChainAccess"),
.package(name: "WalletFoundation", path: "../WalletFoundation"),
.package(name: "WalletNetwork", path: "../WalletNetwork"),
.package(name: "CryptoSwift", path: "../../Vendors/spm/CryptoSwift-1.5.1")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "WalletKit",
dependencies: ["CryptoSwift", "eosswift", "KeyChainAccess", "WalletFoundation", "WalletNetwork"],
path: "./Sources"),
.testTarget(
name: "WalletKitTests",
dependencies: ["WalletKit"],
path: "Tests"//, // Test files
// resources: [.copy("TestData")] // The test data files, copy files without modifying them
)
]
)
@@ -1,53 +0,0 @@
//
// DeepLink.swift
//
//
// Created by Juraldinio on 8/9/22.
//
import Foundation
public struct DeepLink {
public typealias DataParams = [AnyHashable: Any]
public typealias InfoParams = [String: Any]
public let action: DeepLinkAction
let data: Data?
public let info: [String: Any]?
public let url: URL?
public static func create(params: DataParams?) -> DeepLink? {
guard let params = params,
let rawAction = params["action"] as? String,
let action = DeepLinkAction(rawValue: rawAction),
let data = (params["params"] as? String)?.data(using: .utf8)
?? (try? JSONSerialization.data(withJSONObject: params["params"] as? [String: Any], options: [])) else {
return nil
}
return DeepLink(action: action, data: data, info: nil, url: URL(string: params["r"] as? String ?? ""))
}
public static func create(info: DataParams?) -> DeepLink? {
guard let info = info as? [String: Any],
let rawAction = info["action"] as? String,
let action = DeepLinkAction(rawValue: rawAction) else {
return nil
}
return DeepLink(action: action, data: nil, info: info, url: nil)
}
public func get<T: Decodable>(_ model: T.Type) -> T? {
guard let data = self.data else { return nil }
return try? JSONDecoder().decode(T.self, from: data)
}
public func dictionary() -> [String: Any] {
guard let data = self.data else { return [:] }
return (try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]) ?? [:]
}
}
@@ -1,136 +0,0 @@
//
// Village.swift
//
//
// Created by Juraldinio on 15.08.2022.
//
import Foundation
import WalletFoundation
import WalletNetwork
final public class Village {
private static var keychainKeeper = PrivacyKeeper(keychain: WalletKeychain.instance)
private static var villages = [Village]()
private let environment: NetworkEnvironment
private var houses = [VillageHouse]()
private var foreigner: Foreigner? {
didSet {
guard let foreigner = self.foreigner else { return }
ApplicationSettings.device(description: "\(foreigner)")
}
}
// MARK: - Init
private init(environment: NetworkEnvironment) {
self.environment = environment
}
// MARK: - Public
/// Create instance of Village.
public static func get(with environment: NetworkEnvironment) -> Village {
if let village = Self.villages.first(where: { $0.environment.isEquals(other: environment) }) {
return village
}
let village = Village(environment: environment)
Self.villages.append(village)
return village
}
/// Get device instance.
public func device(force: Bool = false) async throws -> Device {
if force { self.foreigner = nil }
if let foreigner = self.foreigner { return foreigner }
if !force,
let foreigner = Foreigner.restore() {
do {
try await foreigner.retrievStatus(using: self.environment)
self.foreigner = foreigner
return foreigner
} catch { }
}
do {
let foreigner = try await Foreigner.create(for: DeviceUUID.shared.device, using: self.environment)
foreigner.save()
self.foreigner = foreigner
return foreigner
} catch {
throw DeviceError.create
}
}
public func attestate(device: Device) async throws -> CertifiedDevice {
try await Resident.permit(for: device, using: self.environment)
}
/// Create wallet key.
public func createWalletKey() throws -> WalletKey {
try WalletKey.create()
}
/// Create wallet case for hold wallet.
public func createWalletCase(with name: String, key: WalletKey? = nil) async throws -> WalletCase {
let walletKey: WalletKey
if let key = key {
walletKey = key
} else {
walletKey = try WalletKey.create()
}
return try await VillagerCoat.create(name: name, using: self.environment, key: walletKey)
}
/// Create bank that contain Wallets.
public func getBank(with password: String, on device: Device) throws -> Bank {
guard Self.keychainKeeper.accept(password: password) else {
throw WalletError.passwordNotMatch
}
if let bank = self.houses.first(where: { $0.isEquals(device: device, environment: self.environment, password: password) }) {
return bank
}
let house = VillageHouse.create(password: password,
keeper: Self.keychainKeeper,
on: device,
environment: self.environment)
self.houses.append(house)
return house
}
public static func reset() {
VillageHouse.removeAllVillagers()
Self.keychainKeeper.reset()
}
// Keychain static methods
public static func accept(password: String) -> Bool {
Self.keychainKeeper.accept(password: password)
}
public static func passwordViaBiometrics() -> String? {
Self.keychainKeeper.passwordByBiometrics()
}
public static var isPasswordExists: Bool {
Self.keychainKeeper.isPasswordExists
}
public static func set(password: String, old: String? = nil) throws {
try Self.keychainKeeper.update(password: password, old: old)
}
}
@@ -1,93 +0,0 @@
//
// Tractor.swift
//
//
// Created by Juraldinio on 15.08.2022.
//
import Foundation
import Combine
import WalletFoundation
import WalletNetwork
import KeyChainAccess
final class Foreigner: Device, Codable {
private enum Constant {
static let saveKey = "village.tractor.instance"
static let service = CodingUserInfoKey(rawValue: "service")!
}
// MARK: - Codable
private enum CodingKeys: String, CodingKey {
case uuid
case id
}
// MARK: - Init
private init(uuid: String, id: String, isTrusted: Bool) {
self.uuid = uuid
self.id = id
self.isTrusted = isTrusted
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(String.self, forKey: .uuid)
self.id = try container.decode(String.self, forKey: .id)
}
// MARK: - Device
let uuid: String
let id: String
private(set) var isTrusted: Bool = false
private(set) var availableAccounts: Int?
// MARK: - CustomStringConvertible
var description: String { "uuid: \(self.uuid), id: \(self.id)" }
// MARK: - Methods
func save() {
guard let rawData = try? JSONEncoder().encode(self) else { return }
let keychain = Keychain()
try? keychain.set(rawData, key: Constant.saveKey)
}
func retrievStatus(using environment: NetworkEnvironment) async throws {
let service = DeviceService(environment: environment)
do {
let status = try await service.stateDevice(id: self.id)
self.availableAccounts = status.availableAccounts
self.isTrusted = status.isTrusted
return
} catch {
throw DeviceError.status
}
}
// MARK: - Static
static func create(for cid: String, using environment: NetworkEnvironment) async throws -> Foreigner {
let service = DeviceService(environment: environment)
do {
let device = try await service.createDevice(uuid: cid)
return Foreigner(uuid: device.uuid, id: device.id, isTrusted: device.isTrusted)
} catch {
throw DeviceError.create
}
}
static func restore() -> Foreigner? {
let keychain = Keychain()
guard let rawData = try? keychain.getData(Constant.saveKey) else { return nil }
let decoder = JSONDecoder()
return try? decoder.decode(Foreigner.self, from: rawData)
}
}
@@ -1,230 +0,0 @@
//
// PrivacyKeeper.swift
//
//
// Created by Nut.Tech on 07.02.2023.
//
import WalletFoundation
import Foundation
import CryptoSwift
final class PrivacyKeeper {
private typealias CipherData = (key: String, iv: String)
private enum Constants {
static let oldPasswordKey = CommonKey("PrivacyKeeper.password")
static let passwordKey = "PrivacyKeeper.password"
static let cipherKey = "PrivacyKeeper.ckey"
static let ivKey = "PrivacyKeeper.ivkey"
}
var isPasswordExists: Bool {
if let encryptedPasswordKey {
return self.keychain.exist(encryptedPasswordKey)
}
return false
}
private var encryptedPasswordKey: CommonKey?
private var cipherData: CipherData
private var keychain: KeychainProtocol
init(keychain: KeychainProtocol) {
self.keychain = keychain
self.cipherData = Self.initCipherData()
self.encryptedPasswordKey = Self.makeEncryptedPasswordCommonKey(cipherData: self.cipherData)
}
/// Cheks if password correct
func accept(password: String) -> Bool {
self.getPassword(password) == password
}
/// Get password via biometry
func passwordByBiometrics() -> String? {
guard let encryptedPasswordKey else { return nil }
return self.keychain.bioExist(encryptedPasswordKey) ?
self.keychain[biometric: encryptedPasswordKey] : nil
}
/// Get private key from keychain
/// - Parameters:
/// - key: common key in keychain
/// - password: current password
/// - Returns: private key (if exists)
func privateKey(for key: String, password: String?) -> String? {
let pwd = password ?? self.passwordByBiometrics()
if let pwd {
return self.keychain[self.encryptPK(commonKey: key), password: pwd]
} else {
return nil
}
}
/// Update password to new one.
func update(password: String, old: String? = nil) throws {
guard self.isPasswordExists else {
try self.setPassword(password)
return
}
guard let old, self.accept(password: old) else {
throw WalletError.passwordNotMatch
}
try self.setPassword(password)
}
/// Update private key for current account. If you are using biometrics, pass nil in password.
/// - Parameters:
/// - privateKey: new private key
/// - key: common key in keychain
/// - password: current password
func update(privateKey: String?, for key: String, password: String?) {
let pwd = password ?? self.passwordByBiometrics()
guard let pwd, self.accept(password: pwd) else { return }
self.keychain[self.encryptPK(commonKey: key), password: pwd] = privateKey
}
/// Migrates pasword and private key storage in keychain to new style
/// - Parameters:
/// - oldKey: old private key common key for keychain
/// - newKey: new private key common key for keychain
/// - password: current password
func migrate(oldKey: CommonKey, newKey: CommonKey, password: String) {
self.migratePassword(password: password)
guard self.accept(password: password) else { return }
self.migratePrivateKey(oldKey: oldKey, newKey: newKey, password: password)
self.migrateToEncrypted(commonKey: newKey, password: password)
}
/// Resets all data by making all old data inaccessible
func reset() {
self.cipherData = Self.createCipherData()
self.encryptedPasswordKey = Self.makeEncryptedPasswordCommonKey(cipherData: self.cipherData)
}
/// Gets password if exists. Pass nil to get biometrics password
/// - Parameter password: current password
/// - Returns: password string (if exists)
private func getPassword(_ password: String? = nil) -> String? {
if let password {
guard let encryptedPasswordKey else { return nil }
return self.keychain[encryptedPasswordKey, password: password]
} else {
return self.passwordByBiometrics()
}
}
/// Set password
/// - Parameter password: new password
private func setPassword(_ password: String) throws {
guard let encryptedPasswordKey else {
throw WalletError.passwordKeyNotExist
}
self.keychain[encryptedPasswordKey, password: password] = password
}
/// Migrate private key in keychain from old to new for old versions
/// - Parameters:
/// - oldKey: old-style key
/// - newKey: new-style key
/// - password: current password
private func migratePrivateKey(oldKey: CommonKey, newKey: CommonKey, password: String) {
guard let privateKey = self.keychain[oldKey, password: password] else { return }
self.keychain[oldKey, password: password] = nil
self.keychain[newKey, password: password] = privateKey
}
/// Migrate to encrypted keys for keychain
/// - Parameters:
/// - commonKey: old-style common key, without encryption
/// - password: current password
private func migrateToEncrypted(commonKey: CommonKey, password: String) {
guard let privateKey = self.keychain[commonKey, password: password] else { return }
self.keychain[commonKey, password: password] = nil
self.keychain[self.encryptPK(commonKey: commonKey.rawValue), password: password] = privateKey
}
/// Migrating from an old keychain password key to a new one
/// - Parameter password: current password
private func migratePassword(password: String) {
guard self.keychain[Constants.oldPasswordKey, password: password] != nil,
let encryptedPasswordKey else { return }
self.keychain[Constants.oldPasswordKey, password: password] = nil
self.keychain[encryptedPasswordKey, password: password] = password
}
/// Makes encrypted password key for keychain
private static func makeEncryptedPasswordCommonKey(cipherData: CipherData) -> CommonKey? {
guard let encryptedCKeyString = try? Self.aesEncrypt(string: Constants.passwordKey,
key: cipherData.key,
iv: cipherData.iv)
else { return nil }
return CommonKey(encryptedCKeyString)
}
/// Encrypt key for use in keychain
/// - Parameter commonKey: common key, e.g. "user@wallet.privatekey"
/// - Returns: encrypted CommonKey object
private func encryptPK(commonKey: String) -> CommonKey {
let encryptedCKeyString = try? Self.aesEncrypt(string: commonKey,
key: self.cipherData.key,
iv: self.cipherData.iv)
return CommonKey(encryptedCKeyString ?? commonKey)
}
/// Generates data for encoding keys
private static func makeCipherData() -> CipherData {
let ckey = UUID().uuidString.replacingOccurrences(of:"-",
with: "",
options: .literal)
let iv = String(UUID().uuidString.replacingOccurrences(of:"-",
with: "",
options: .literal)
.dropLast(16))
return CipherData(key: ckey, iv: iv)
}
/// Tries to load cipher data and creates it, if doesn't exist
private static func initCipherData() -> CipherData {
if let ckey = UserDefaults.standard.string(forKey: Constants.cipherKey),
let iv = UserDefaults.standard.string(forKey: Constants.ivKey) {
return CipherData(key: ckey, iv: iv)
} else {
return Self.createCipherData()
}
}
/// Creates new data for encoding keychain keys and saves it to defaults
private static func createCipherData() -> CipherData {
let cipherData = Self.makeCipherData()
UserDefaults.standard.set(cipherData.key, forKey: Constants.cipherKey)
UserDefaults.standard.set(cipherData.iv, forKey: Constants.ivKey)
return cipherData
}
static func aesEncrypt(string: String, key: String, iv: String) throws -> String {
guard let data = string.data(using: .utf8) else {
throw WalletError.cannotEncryptNonUTF8Data
}
let encrypted = try AES(key: Array(key.utf8),
blockMode: CBC(iv: Array(iv.utf8)))
.encrypt([UInt8](data))
let encryptedData = Data(encrypted)
return encryptedData.base64EncodedString()
}
static func aesDecrypt(string: String, key: String, iv: String) throws -> String {
guard let data = Data(base64Encoded: string) else {
throw WalletError.cannotDecryptNonUTF8Data
}
let decrypted = try AES(key: Array(key.utf8),
blockMode: CBC(iv: Array(iv.utf8)))
.decrypt([UInt8](data))
let decryptedData = Data(decrypted)
guard let decryptedString = String(bytes: decryptedData.bytes, encoding: .utf8) else {
throw WalletError.cannotDecryptData
}
return decryptedString
}
}
@@ -1,47 +0,0 @@
//
// Resident.swift
//
//
// Created by Juraldinio on 25.08.2022.
//
import Foundation
import WalletFoundation
import WalletNetwork
struct Resident: CertifiedDevice {
let device: Device
let status: DeviceStatus
static func permit(for device: Device, using environment: NetworkEnvironment) async throws -> CertifiedDevice {
if device.isTrusted { return Resident(device: device, status: .valid) }
let result = await DeviceCheckToken.generate()
guard let deviceToken = try? result.get() else {
throw CertifiedDeviceError.token
}
let service = DeviceService(environment: environment)
do {
let status = try await service.checkDevice(id: device.id, token: deviceToken.token)
return Resident(device: device, status: status == .valid ? .valid : .invalid )
} catch let NetworkServiceError.gqlApplication(error) {
let deviceError: CertifiedDeviceError
switch error {
case "Invalid": deviceError = .invalid
case "DeviceNotFoundError": deviceError = .notFound
case "DecryptError": deviceError = .decrypt
case "UnexpectedDeviceKind": deviceError = .kind
case "UnexpectedError": deviceError = .unknown
default: deviceError = .unknown
}
throw deviceError
} catch {
throw CertifiedDeviceError.unknown
}
}
}
@@ -1,300 +0,0 @@
//
// File.swift
//
//
// Created by Juraldinio on 8/27/22.
//
import Foundation
import Combine
import WalletFoundation
import WalletNetwork
final class VillageHouse: Bank {
private enum Constants {
static let oldKey = "Account.Service.collection"
static let key = "Wallet.bank.service"
enum Keys {
static let current = CommonKey("Account.Service.current")
static let collection = CommonKey("Account.Service.collection")
}
}
// MARK: - Properties
private let device: Device
private let environment: NetworkEnvironment
private let keychainKeeper: PrivacyKeeper
private let activeSubject: CurrentValueSubject<Villager?, Never>
private let villagersSubject: CurrentValueSubject<[Villager], Never>
private var cancellables = Set<AnyCancellable>()
// MARK: - Init
private init(password: String, keychain: PrivacyKeeper, device: Device, environment: NetworkEnvironment) {
self.device = device
self.environment = environment
self.keychainKeeper = keychain
let collection = Self.restore(keychain: keychain)
Self.migrateIfNeeded(collection: collection, password: password)
self.villagersSubject = CurrentValueSubject(collection)
let active = Self.active(in: collection, keychain: keychain)
self.activeSubject = CurrentValueSubject(active)
self.villagersSubject
.sink { [weak self] villagers in
self?.save(villagers: villagers)
}
.store(in: &self.cancellables)
}
// MARK: - Bank
var active: Wallet? { self.activeSubject.value }
var wallets: [Wallet] { self.villagersSubject.value }
lazy var activePublisher: AnyPublisher<Wallet?, Never> = self.activeSubject.map { $0 }.eraseToAnyPublisher()
lazy var walletsPublisher: AnyPublisher<[Wallet], Never> = self.villagersSubject.map { $0 }.eraseToAnyPublisher()
func remove(wallet: Wallet) throws {
var villagers = self.villagersSubject.value
guard let villager = self.villager(by: wallet),
let index = villagers.firstIndex(where: { $0 == villager }) else {
throw BankError.notOwned
}
villagers.remove(at: index)
villager.clear()
self.villagersSubject.value = villagers
if let active = self.active,
villager.isEquals(other: active) {
try self.activate(wallet: nil)
}
}
func activate(wallet: Wallet?) throws {
guard let wallet else {
Settings.shared[Constants.Keys.current] = Data()
self.activeSubject.value = nil
return
}
guard let villager = self.villager(by: wallet) else {
throw BankError.notOwned
}
// We can save only wallet in accepted state.
if case .accepted = villager.state {
Settings.shared[Constants.Keys.current] = villager.jsonData()
}
self.activeSubject.value = villager
}
func add(using walletCase: WalletCase, password: String) async throws -> Wallet {
let villager = try await Villager.create(walletCase: walletCase,
on: self.device,
using: self.environment,
keychain: self.keychainKeeper)
self.add(wallets: [villager], password: password)
return villager
}
func add(using purses: [Purse], password: String) throws {
guard self.keychainKeeper.accept(password: password) else {
throw WalletError.passwordNotMatch
}
let wallets = purses
.filter { $0.bank?.isEquals(other: self, password: password) ?? false }
.filter { purse in
!self.wallets.contains(where: { $0.name == purse.name && $0.keyType.rawValue == purse.permission.permName })
}
.map { Villager.create(purse: $0,
keeper: self.keychainKeeper) }
self.add(wallets: wallets, password: password)
}
func restore(using keys: WalletKey) async throws -> PurseHolder {
let holder = PurseHolder()
let service = AccountHyperionService(environment: self.environment)
let eosService = EOSService(environment: self.environment)
return try await holder.load(using: service, eosService: eosService, keys: keys, in: self)
}
func refreshStatus(wallet: Wallet?) async throws {
let villagers: [Villager]
if let wallet,
let villager = self.villager(by: wallet) {
villagers = [villager]
} else {
villagers = self.villagersSubject.value
}
_ = await withTaskGroup(of: Void.self) { group in
villagers.forEach { villager in
switch villager.state {
case .pending,
.creating:
group.addTask {
try? await villager.refresh(using: self.environment)
}
case .accepted,
.declined:
return
}
}
await group.waitForAll()
self.save(villagers: villagers)
}
}
func accept(password: String) -> Bool {
self.keychainKeeper.accept(password: password)
}
func isEquals(other: Bank, password: String) -> Bool {
guard let house = other as? VillageHouse else { return false }
return self.device.isEquals(other: house.device) &&
self.environment.isEquals(other: house.environment) &&
self.keychainKeeper.accept(password: password) &&
other.accept(password: password)
}
func switchPassword(_ new: String, old: String) throws {
try self.keychainKeeper.update(password: new, old: old)
self.wallets.forEach { $0.updatePrivateKeyEncryption(password: new, old: old) }
}
func update(_ keyUpdates: [WalletKeyUpdate], using password: String) async throws -> [WalletKeyUpdateResult] {
// TODO: - After move EOS to frameworks!
/*guard password == self.password else { throw BankError.passwordNotMatch }
let villagers = keyUpdates.compactMap({ self.villager(by: $0.wallet) })
guard villagers.count != keyUpdates.count else { throw BankError.notOwned }
guard let privateKey = self.privateKey(password) else {
throw WalletError.privateKeyNotExists
}
return WalletKeyUpdate(wallet: self, oldPrivateKey: "", transitionId: "")
return try await villager.update(key: key, password: self.password, using: self.environment)*/
return []
}
// MARK: - Internal
func isEquals(device: Device, environment: NetworkEnvironment, password: String) -> Bool {
return self.device.isEquals(other: device) &&
self.environment.isEquals(other: environment) &&
self.accept(password: password)
}
// MARK: - Private
private func add(wallets: [Villager], password: String) {
wallets
.filter { $0.key.privateKey.isExist }
.forEach { $0.updateKeychain(key: $0.key, using: password) }
let villagers = self.villagersSubject.value
self.villagersSubject.value = villagers + wallets
}
private func save(villagers: [Villager]) {
Settings.shared[Constants.Keys.collection] = villagers.jsonData()
}
private static func restore(keychain: PrivacyKeeper) -> [Villager] {
if let data: Data = Settings.shared[Constants.Keys.collection],
let userInfoKey = Villager.keychainUserInfoKey,
let villagers: [Villager] = data.jsonDecoded(type: Villager.self,
userInfo: [userInfoKey: keychain]) {
return villagers
}
return []
}
private static func migrateIfNeeded(collection: [Villager], password: String) {
guard collection.count > 0 else { return }
collection.forEach { $0.migrate(password: password) }
}
private func migrate() {
let migrateWallets: [Villager]
if let data = UserDefaults.standard.value(forKey: Constants.oldKey) as? Data,
let collection = try? JSONDecoder().decode([Villager.OldVillager].self, from: data) {
migrateWallets = collection.compactMap { $0.covert(keeper: self.keychainKeeper) }
} else {
migrateWallets = []
}
print(migrateWallets)
// Delete previous key
// UserDefaults.standard.set(nil, forKey: Constants.oldKey)
}
private func villager(by wallet: Wallet) -> Villager? {
guard let villager = wallet as? Villager,
self.villagersSubject.value.contains(where: { $0 == villager }) else {
return nil
}
return villager
}
// MARK: - Static
public static func create(password: String, keeper: PrivacyKeeper, on device: Device, environment: NetworkEnvironment) -> VillageHouse {
VillageHouse(password: password, keychain: keeper, device: device, environment: environment)
}
public static func removeAllVillagers() {
Settings.shared[Constants.Keys.collection] = Data()
}
private static func active(in collection: [Villager], keychain: PrivacyKeeper) -> Villager? {
if let data: Data = Settings.shared[Constants.Keys.current],
let userInfoKey = Villager.keychainUserInfoKey,
let villager: Villager = data.jsonDecoded(type: Villager.self,
userInfo: [userInfoKey: keychain]),
collection.contains(where: { $0 == villager }) {
return villager
}
guard let rawValue: String = Settings.shared[Constants.Keys.current] else { return nil }
let name = rawValue.components(separatedBy: "@").first
let keyTypeString = rawValue.components(separatedBy: "@").last ?? ""
let keyType = WalletKeyType(rawValue: keyTypeString) ?? WalletKeyType.active
return collection.first(where: { $0.name == name && $0.keyType == keyType })
}
}
@@ -1,283 +0,0 @@
//
// Villager.swift
//
//
// Created by Juraldinio on 16.08.2022.
//
import Foundation
import Combine
import WalletFoundation
import WalletNetwork
extension String {
fileprivate static let privateKey = "privateKey"
}
final class Villager: Wallet, Codable, CustomStringConvertible {
struct OldVillager: Codable {
var username: String
var publicKey: String
var keyType: String
func covert(keeper: PrivacyKeeper) -> Villager? {
guard let keyType = WalletKeyType(rawValue: self.keyType),
let key = try? WalletKey.restore(using: self.username, type: keyType, publicKey: self.publicKey) else {
return nil
}
return Villager(name: self.username,
key: key,
keyType: keyType,
state: .accepted,
keychain: keeper)
}
}
private let stateSubject: CurrentValueSubject<WalletState, Never>
// MARK: - Init
private init(walletCase: WalletCase,
keyType: WalletKeyType,
state: WalletState,
keychain: PrivacyKeeper) {
self.name = walletCase.name
self.key = walletCase.key
self.keyType = keyType
self.keychain = keychain
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
}
private init(name: String,
key: WalletKey,
keyType: WalletKeyType,
state: WalletState,
keychain: PrivacyKeeper) {
self.name = name
self.key = key
self.keyType = keyType
self.keychain = keychain
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
}
// MARK: - Codable
private enum CodingKeys: String, CodingKey {
// Old values
case username
case publicKey
//
case name
case key
case keyType
case state
}
static var keychainUserInfoKey: CodingUserInfoKey? {
return CodingUserInfoKey(rawValue: "keychain")
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// For save old format with username field!
let username = try container.decodeIfPresent(String.self, forKey: .username)
if let username {
self.name = username
} else {
self.name = try container.decode(String.self, forKey: .name)
}
self.keyType = try container.decode(WalletKeyType.self, forKey: .keyType)
let state = try container.decode(WalletState.self, forKey: .state)
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
guard let keychainKey = Self.keychainUserInfoKey,
let keeper = decoder.userInfo[keychainKey] as? PrivacyKeeper else {
throw WalletError.decodingWithoutKeychainContext
}
self.keychain = keeper
// If first version we hold key on flat structure!
if let key = try? WalletKey(from: decoder) {
self.key = key
} else {
self.key = try container.decode(WalletKey.self, forKey: .key)
}
// TODO: - NEED COMPLETETASK
// self.key = try WalletKey.restore(using: self.name, type: self.keyType, publicKey: publicKey, password: "")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.name, forKey: .name)
try container.encode(self.key, forKey: .key)
try container.encode(self.keyType, forKey: .keyType)
try container.encode(self.stateSubject.value, forKey: .state)
}
// MARK: - Wallet
let name: String
private(set) var key: WalletKey
let keyType: WalletKeyType
var state: WalletState { self.stateSubject.value }
private let keychain: PrivacyKeeper
lazy var statePublisher: AnyPublisher<WalletState, Never> = self.stateSubject.eraseToAnyPublisher()
// MARK: - Equatable
static func == (lhs: Villager, rhs: Villager) -> Bool {
return lhs.name == rhs.name
&& lhs.key == rhs.key
&& lhs.keyType == rhs.keyType
&& lhs.state == rhs.state
&& lhs.name == rhs.name
}
// MARK: - CustomStringConvertible
var description: String { "(name: \(name), keyType: \(keyType), state: \(state), public: \(key.publicKey)" }
// MARK: - Internal
func refresh(using environment: NetworkEnvironment) async throws {
let service = AccountService(environment: environment)
let state: WalletState
switch self.state {
case .creating(let orderId),
.pending(let orderId):
do {
let order = try await service.order(with: orderId)
state = WalletState(order: order)
} catch {
throw WalletError.network(error)
}
default:
state = self.state
}
self.stateSubject.value = state
}
@discardableResult
func updateKeychain(key: WalletKey, using password: String) -> Wallet {
if self.keychain.accept(password: password) {
self.key = key
self.updatePrivateKey(password: password)
}
return self
}
func privateKey(password: String?) -> String? {
guard let password, self.keychain.accept(password: password) else { return nil }
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
return self.keychain.privateKey(for: key.rawValue, password: password)
}
private func updatePrivateKey(password: String) {
guard let privateKey = self.key.privateKey else { return }
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
self.keychain.update(privateKey: privateKey, for: key.rawValue, password: password)
}
// TODO: - Need review
func migrate(password: String) {
let oldKey = CommonKey.key(self.name, suffix: .privateKey)
let newKey = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
self.keychain.migrate(oldKey: oldKey, newKey: newKey, password: password)
}
/// Updates storage of private key in keychain for new password
/// - Parameters:
/// - password: new password
/// - old: old password (which was stored with private key earlier)
func updatePrivateKeyEncryption(password: String, old: String) {
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
let privateKey = self.privateKey(password: old)
self.keychain.update(privateKey: nil, for: key.rawValue, password: old)
self.keychain.update(privateKey: privateKey, for: key.rawValue, password: password)
}
func clear() {
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
self.keychain.update(privateKey: nil, for: key.rawValue, password: "")
}
// MARK: - Static
static func create(purse: Purse, keeper: PrivacyKeeper) -> Villager {
Villager(name: purse.name,
key: purse.key,
keyType: purse.keyType,
state: .accepted,
keychain: keeper)
}
static func create(walletCase: WalletCase,
on device: Device,
using environment: NetworkEnvironment,
keychain: PrivacyKeeper) async throws -> Villager {
let service = AccountService(environment: environment)
do {
// return Villager(walletCase: walletCase, state: .pending)
let order = try await service.create(username: walletCase.name,
pubKey: walletCase.key.publicKey,
deviceId: device.id)
let state: WalletState
switch order.status {
case .creating: state = .creating(order.id)
case .active, .executed: state = .pending(order.id)
case .expired, .failed: state = .declined
case .completed: state = .accepted
}
return Villager(walletCase: walletCase,
keyType: .owner,
state: state,
keychain: keychain)
} catch let NetworkServiceError.gqlApplication(error) {
if ApplicationSettings.ignoreCreateAccountSatus {
return Villager(walletCase: walletCase,
keyType: .owner,
state: .creating("HELLO"),
keychain: keychain)
}
let walletError: WalletError
switch error {
case "invalid EOS public key": walletError = .invalidKey
case "INVALID DEVICE ID": walletError = .invalidDevice
case "UNKNOWN DEVICE": walletError = .device
case "UNCHECKED": walletError = .unchecked
case "UNTRUSTED": walletError = .untrusted
case "DEVICE LIMIT REACHED": walletError = .deviceLimit
case "DAILY LIMIT REACHED": walletError = .dailyLimit
case "UNAVAILABLE": walletError = .unavailable
case "ACCOUNT ALREADY EXISTS": walletError = .exists
default: walletError = .unavailable
}
throw walletError
} catch {
throw WalletError.unavailable
}
}
static func create(walletKey: WalletKey, using environment: NetworkEnvironment) async throws -> Villager {
// TODO: - Add logic
throw WalletError.unavailable
}
}
@@ -1,52 +0,0 @@
//
// VillagerCoat.swift
//
//
// Created by Juraldinio on 16.08.2022.
//
import Foundation
import WalletNetwork
final class VillagerCoat: WalletCase {
// MARK: - Init
private init(name: String, key: WalletKey) {
self.name = name
self.key = key
}
// MARK: - WalletCase
let name: String
private(set) var key: WalletKey
@discardableResult
func update(key: WalletKey) -> WalletKey {
self.key = key
return self.key
}
@discardableResult
func generateKey() -> WalletKey {
if let key = try? WalletKey.create() {
self.key = key
}
return self.key
}
// MARK: - Static
static func create(name: String, using environment: NetworkEnvironment, key: WalletKey) async throws -> VillagerCoat {
let walletName = name.lowercased()
let service = AccountService(environment: environment)
let isAvailable = await service.isAvailable(username: walletName)
switch isAvailable {
case .available: return VillagerCoat(name: walletName, key: key)
case .alreadyTaken: throw WalletCaseError.exists(name: walletName)
case .system: throw WalletCaseError.system
}
}
}
@@ -1,47 +0,0 @@
//
// Bank.swift
//
//
// Created by Juraldinio on 8/27/22.
//
import Foundation
import Combine
import WalletNetwork
public enum BankError: Error {
case empty
case notOwned
}
/// Bank that contain wallets.
public protocol Bank: AnyObject {
/// Current active wallet.
var active: Wallet? { get }
/// Publisher for observe changes.
var activePublisher: AnyPublisher<Wallet?, Never> { get }
/// Wallets in bank.
var wallets: [Wallet] { get }
/// Publisher for observe wallet changes.
var walletsPublisher: AnyPublisher<[Wallet], Never> { get }
/// Activate wallet
func activate(wallet: Wallet?) throws
/// Create new wallet in bank.
func add(using walletCase: WalletCase, password: String) async throws -> Wallet
/// Add Purse to bank with converting to Wallet
func add(using purses: [Purse], password: String) throws
/// Restore wallet by key.
func restore(using keys: WalletKey) async throws -> PurseHolder
/// Remove wallet from bank.
func remove(wallet: Wallet) throws
/// Check wallet status
func refreshStatus(wallet: Wallet?) async throws
/// Update WalletKey for Wallet
func update(_ keyUpdates: [WalletKeyUpdate], using password: String) async throws -> [WalletKeyUpdateResult]
/// Compare two banks
func isEquals(other: Bank, password: String) -> Bool
/// Switch password
func switchPassword(_ password: String, old: String) throws
/// Check if password valid for this bank
func accept(password: String) -> Bool
}
@@ -1,40 +0,0 @@
//
// CertifiedDevice.swift
//
//
// Created by Juraldinio on 25.08.2022.
//
import Foundation
public enum CertifiedDeviceError: Error {
/// Unavailable generate token for check
case token
/// Device not fount on server
case notFound
/// Invalid token
case invalid
/// Token decription error
case decrypt
/// Unexpected device kind
case kind
/// Unknown error
case unknown
/// Already in progress
case progress
}
/// Device status
public enum DeviceStatus {
/// Valid
case valid
/// Invalid
case invalid
}
public protocol CertifiedDevice {
/// Device
var device: Device { get }
/// Device status
var status: DeviceStatus { get }
}
@@ -1,40 +0,0 @@
//
// Device.swift
//
//
// Created by Juraldinio on 15.08.2022.
//
import Foundation
import Combine
/// Errors for Device
public enum DeviceError: Error {
/// Failed while restore
case restore
/// Failed create device on server side
case create
/// Failed retrieve status
case status
}
public protocol Device: CustomStringConvertible {
/// Device generated Identity UUID
var uuid: String { get }
/// Server generated Identity
var id: String { get }
/// Is device trusted
var isTrusted: Bool { get }
func isEquals(other: Device) -> Bool
}
// MARK: - Equatable
extension Device {
func isEquals(other: Device) -> Bool {
return self.uuid == other.uuid &&
self.id == other.id &&
self.isTrusted == other.isTrusted
}
}
@@ -1,12 +0,0 @@
//
// ErrorDomain.swift
//
//
// Created by user on 31.10.2022.
//
import Foundation
enum ErrorDomain: Error {
case noEnvironment
}
@@ -1,101 +0,0 @@
//
// PurseHolder.swift
//
//
// Created by Juraldinio on 12/4/22.
//
import Foundation
import WalletFoundation
import WalletNetwork
import EosioSwift
import Combine
public struct Purse {
public let name: String
public let key: WalletKey
public let keyType: WalletKeyType
let permission: Permission
var bank: Bank?
}
public final class PurseHolder {
public enum PurseError: Error {
case empty
}
public private(set) var purses: [Purse] = []
private var count: Int = 0
func load(using service: AccountHyperionService, eosService: EOSService, keys: WalletKey, in bank: Bank) async throws -> PurseHolder {
let publicKey = keys.publicKey
let collection = await service.fetchNamesHyperion(publicKey: publicKey)
guard !collection.isEmpty else {
throw PurseError.empty
}
let _ = await withCheckedContinuation { continuation in
self.count = collection.count
collection.forEach { name in
DispatchQueue.global().async {
eosService.getAccount(name) { response in
self.count -= 1
switch response {
case let .success(accountInfo):
let permissions = accountInfo.permissions
.filter { permission in permission.requiredAuth.keys.contains(where: { $0.key == publicKey }) }
.sorted { $0.permName > $1.permName }
permissions.forEach { permission in
if (permission.permName == WalletKeyType.owner.rawValue) {
self.purses.append(Purse(name: name,
key: keys,
keyType: .owner,
permission: permission,
bank: bank))
}
let hasAddedOwnerKey = self.purses.contains(where: { account in
account.name == name &&
account.permission.requiredAuth.keys.first?.key == permission.requiredAuth.keys.first?.key
})
if let type = WalletKeyType(rawValue: permission.permName),
type != WalletKeyType.owner,
!hasAddedOwnerKey {
self.purses.append(Purse(name: name,
key: keys,
keyType: type,
permission: permission,
bank: bank))
}
}
default: break
}
if self.count == 0 {
continuation.resume(returning: self.purses)
}
}
}
}
}
return self
}
}
@@ -1,184 +0,0 @@
//
// Wallet.swift
//
//
// Created by Juraldinio on 15.08.2022.
//
import Foundation
import WalletNetwork
import Combine
/// Errors for wallet creation
public enum WalletError: Error {
/// Invalid EOS public key
case invalidKey
/// Device not found
case invalidDevice
/// Device not found
case device
/// Device does not do check phase
case unchecked
/// Device not trusted
case untrusted
/// Device limit reached
case deviceLimit
/// Daily limit reached
case dailyLimit
/// Unknown error
case unavailable
/// Network error
case network(Error)
/// Private key does not exists
case privateKeyNotExists
/// Wallet with such name already exists.
case exists
/// Input password doesn't match keychain password
case passwordNotMatch
/// Error creating access key for password in keychain
case passwordKeyNotExist
/// Failed encryption access key for keychain
case cannotEncryptNonUTF8Data
/// Failed decryption access key for keychain
case cannotDecryptNonUTF8Data
/// Common decrypt error
case cannotDecryptData
/// Trying to decode Wallet-protocol object without keychain data
case decodingWithoutKeychainContext
}
/// Wallet state
public enum WalletState: RawRepresentable {
/// Transaction sent to EOS.
case creating(String)
/// Creation in progress
case pending(String)
/// Created
case accepted
/// Creation failed
case declined
private enum Constants {
static let preffixCreate = "C"
static let preffixPending = "P"
}
// TODO:- remove public
public init(order: WalletOrder) {
switch order.status {
case .completed:
self = .accepted
case .creating:
self = .creating(order.id)
case .executed, .active:
self = .pending(order.id)
case .failed, .expired:
self = .declined
}
}
// MARK: - RawRepresentable
public init?(rawValue: String) {
switch rawValue {
case "accepted": self = .accepted
case "declined": self = .declined
default:
let value = String(rawValue.dropFirst())
let preffix = rawValue.first?.uppercased()
if preffix == Constants.preffixPending {
self = .pending(value)
} else {
self = .creating(value)
}
}
}
public var rawValue: String {
switch self {
case .accepted: return "accepted"
case .declined: return "declined"
case let .pending(value): return "\(Constants.preffixPending)\(value)"
case let .creating(value): return "\(Constants.preffixCreate)\(value)"
}
}
}
extension WalletState: Codable { }
public enum WalletKeyType: String, Codable, CustomStringConvertible {
case owner
case active
public var description: String { self.rawValue }
}
public struct WalletKeyUpdateResult {
public let wallet: Wallet
public let oldPrivateKey: String
public let transitionId: String
}
public struct WalletKeyUpdate {
public let wallet: Wallet
public let key: WalletKey
public init(wallet: Wallet, key: WalletKey) {
self.wallet = wallet
self.key = key
}
}
/// Wallet
public protocol Wallet: AnyObject {
/// Name
var name: String { get }
/// Keys
var key: WalletKey { get }
/// Type
var keyType: WalletKeyType { get }
/// State
var state: WalletState { get }
var statePublisher: AnyPublisher<WalletState, Never> { get }
@discardableResult
func updateKeychain(key: WalletKey, using password: String) -> Wallet
func privateKey(password: String?) -> String?
func updatePrivateKeyEncryption(password: String, old: String)
/// Compare two Wallets
func isEquals(other: Wallet) -> Bool
}
public extension Wallet {
var isOnCreationState: Bool {
switch self.state {
case .declined,
.accepted:
return false
case .creating,
.pending:
return true
}
}
var isOnActiveState: Bool {
switch self.state {
case .creating,
.accepted:
return true
case .declined,
.pending:
return false
}
}
func isEquals(other: Wallet) -> Bool {
self.name == other.name &&
self.key == other.key &&
self.keyType == other.keyType &&
self.state == other.state
}
}
@@ -1,30 +0,0 @@
//
// WalletCase.swift
//
//
// Created by Juraldinio on 17.08.2022.
//
import Foundation
/// Wallet Case errors
public enum WalletCaseError: Error {
/// System error
case system
/// Wallet with name already exists
case exists(name: String)
}
/// Wallet case. This mean case that can contain wallet.
public protocol WalletCase: AnyObject {
/// Name
var name: String { get }
/// Key
var key: WalletKey { get }
/// Update key
@discardableResult
func update(key: WalletKey) -> WalletKey
/// Generate new key
@discardableResult
func generateKey() -> WalletKey
}
@@ -1,142 +0,0 @@
//
// WalletKey.swift
//
//
// Created by Juraldinio on 17.08.2022.
//
import Foundation
import eosswift
/// Errors for WalletKey
public enum WalletKeyError: Error {
/// System error
case system
/// Invalid public key
case invalidPublic
/// Invalid private key
case invalidPrivate
/// Invalid pair public and private keys
case invalidPair
///
case unlock
}
/// Represents Wallet key.
/// This keys must pass checks.
public enum WalletKey: Codable, Equatable {
/// Public key
case publicKey(String)
/// Public and Private key
case bunch(publicKey: String, privateKey: String)
public var publicKey: String {
switch self {
case let .publicKey(value): return value
case let .bunch(publicKey: value, privateKey: _): return value
}
}
public var privateKey: String? {
switch self {
case let .bunch(publicKey: _, privateKey: value): return value
default: return nil
}
}
func save() {
}
// MARK: - Codable
private enum CodingKeys: String, CodingKey {
case publicKey
case privateKey
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let publicKey = try container.decode(String.self, forKey: .publicKey)
let privateKey = try container.decodeIfPresent(String.self, forKey: .privateKey)
if let privateKey {
self = .bunch(publicKey: publicKey, privateKey: privateKey)
} else {
self = .publicKey(publicKey)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .publicKey(value):
try container.encode(value, forKey: .publicKey)
case let .bunch(publicKey: publicKey, privateKey: privateKey):
try container.encode(publicKey, forKey: .publicKey)
try container.encode(privateKey, forKey: .privateKey)
}
}
// MARK: - Public static
public static func create(public: String, private: String) throws -> WalletKey {
let privateKey: EOSPrivateKey
do {
privateKey = try EOSPrivateKey(base58: `private`)
} catch {
throw WalletKeyError.invalidPrivate
}
let publicKey: EOSPublicKey
do {
publicKey = try EOSPublicKey(base58: `public`)
} catch {
throw WalletKeyError.invalidPublic
}
guard privateKey.publicKey.base58 == publicKey.base58 else {
throw WalletKeyError.invalidPair
}
return .bunch(publicKey: publicKey.base58, privateKey: privateKey.base58)
}
public static func create(public: String) throws -> WalletKey {
let publicKey: EOSPublicKey
do {
publicKey = try EOSPublicKey(base58: `public`)
} catch {
throw WalletKeyError.invalidPublic
}
return .publicKey(publicKey.base58)
}
public static func create(private: String) throws -> WalletKey {
let privateKey: EOSPrivateKey
do {
privateKey = try EOSPrivateKey(base58: `private`)
} catch {
throw WalletKeyError.invalidPrivate
}
return .bunch(publicKey: privateKey.publicKey.base58, privateKey: privateKey.base58)
}
/// Create key for wallet
public static func create() throws -> WalletKey {
guard let key = try? EOSPrivateKey() else { throw WalletKeyError.system }
return .bunch(publicKey: key.publicKey.base58, privateKey: key.base58)
}
// MARK: - Internal static
static func restore(using name: String, type: WalletKeyType, publicKey: String) throws -> WalletKey {
throw WalletKeyError.unlock
}
}
@@ -1,58 +0,0 @@
//
// DeepLinkActionTests.swift
//
//
// Created by Juraldinio on 31.10.2022.
//
import Foundation
import XCTest
import WalletKit
final class DeepLinkActionTests: XCTestCase {
func testConvertFromString() {
XCTAssertEqual(DeepLinkAction.connect.rawValue, "connect_accounts")
XCTAssertEqual(DeepLinkAction.chat.rawValue, "chat")
XCTAssertEqual(DeepLinkAction.transfer.rawValue, "transfer")
XCTAssertEqual(DeepLinkAction.walletAuth.rawValue, "wallet_auth")
XCTAssertEqual(DeepLinkAction.tokenization.rawValue, "accept_tokenization")
XCTAssertEqual(DeepLinkAction.approveBuy.rawValue, "approve_buy")
XCTAssertEqual(DeepLinkAction.emission.rawValue, "emission")
XCTAssertEqual(DeepLinkAction.chatMessage.rawValue, "chat.message")
XCTAssertEqual(DeepLinkAction.transactionTransfer.rawValue, "transaction.incoming_transfer")
XCTAssertEqual(DeepLinkAction.transactionInheritance.rawValue, "transactions.incoming_inheritance")
XCTAssertEqual(DeepLinkAction.transactionEmission.rawValue, "transactions.emission")
XCTAssertEqual(DeepLinkAction.p2pDealNew.rawValue, "orders.new_deal")
XCTAssertEqual(DeepLinkAction.p2pDealComplete.rawValue, "orders.completed_deal")
XCTAssertEqual(DeepLinkAction.p2pDealCancel.rawValue, "orders.cancelled_deal")
XCTAssertEqual(DeepLinkAction.p2pDealDispute.rawValue, "orders.dispute_deal")
XCTAssertEqual(DeepLinkAction.news.rawValue, "news")
XCTAssertEqual(DeepLinkAction.competitivePrice.rawValue, "competitive_price")
}
func testConvertToString() {
XCTAssertEqual(DeepLinkAction(rawValue: "connect_accounts"), .connect)
XCTAssertEqual(DeepLinkAction(rawValue: "chat"), .chat)
XCTAssertEqual(DeepLinkAction(rawValue: "transfer"), .transfer)
XCTAssertEqual(DeepLinkAction(rawValue: "wallet_auth"), .walletAuth)
XCTAssertEqual(DeepLinkAction(rawValue: "accept_tokenization"), .tokenization)
XCTAssertEqual(DeepLinkAction(rawValue: "approve_buy"), .approveBuy)
XCTAssertEqual(DeepLinkAction(rawValue: "emission"), .emission)
XCTAssertEqual(DeepLinkAction(rawValue: "chat.message"), .chatMessage)
XCTAssertEqual(DeepLinkAction(rawValue: "transaction.incoming_transfer"), .transactionTransfer)
XCTAssertEqual(DeepLinkAction(rawValue: "transactions.incoming_inheritance"), .transactionInheritance)
XCTAssertEqual(DeepLinkAction(rawValue: "transactions.emission"), .transactionEmission)
XCTAssertEqual(DeepLinkAction(rawValue: "orders.new_deal"), .p2pDealNew)
XCTAssertEqual(DeepLinkAction(rawValue: "orders.completed_deal"), .p2pDealComplete)
XCTAssertEqual(DeepLinkAction(rawValue: "orders.cancelled_deal"), .p2pDealCancel)
XCTAssertEqual(DeepLinkAction(rawValue: "orders.dispute_deal"), .p2pDealDispute)
XCTAssertEqual(DeepLinkAction(rawValue: "news"), .news)
XCTAssertEqual(DeepLinkAction(rawValue: "competitive_price"), .competitivePrice)
}
func testConvertFailed() {
XCTAssertNil(DeepLinkAction(rawValue: "news1"))
}
}
@@ -1,21 +0,0 @@
//
// DeepLinkTests.swift
//
//
// Created by Juraldinio on 31.10.2022.
//
import Foundation
import XCTest
@testable
import WalletKit
final class DeepLinkTests: XCTestCase {
func testCreateWithParamsFailed() {
XCTAssertNil(DeepLink.create(params: nil))
XCTAssertNil(DeepLink.create(params: [:]))
}
}
@@ -1,99 +0,0 @@
//
// JSONParsingTests.swift
//
//
// Created by Nut.Tech on 17.02.2023.
//
import XCTest
@testable import WalletKit
fileprivate struct User: Decodable {
let id: Int
let name: String
var employer: String?
static var employerUserInfoKey: CodingUserInfoKey? {
return CodingUserInfoKey(rawValue: "employer")
}
private enum CodingKeys: String, CodingKey {
case id
case name
}
init(from decoder: Decoder) throws {
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
self.id = try keyedContainer.decode(Int.self, forKey: .id)
self.name = try keyedContainer.decode(String.self, forKey: .name)
guard let employerKey = Self.employerUserInfoKey,
let employer = decoder.userInfo[employerKey] as? String else {
return
}
self.employer = employer
}
}
final class JSONParsingTests: XCTestCase {
private enum Locals {
static let username1 = "Username1"
static let username2 = "Username2"
static let userId1 = 1
static let userId2 = 2
static let companyName = "Company Name"
}
func testJsonDecoded() {
let jsonData = """
{"id": \(Locals.userId1), "name": "\(Locals.username1)"}
""".data(using: .utf8)!
guard let user: User = jsonData.jsonDecoded(type: User.self) else {
XCTFail("Error decoding data")
return
}
XCTAssertEqual(user.id, Locals.userId1)
XCTAssertEqual(user.name, Locals.username1)
}
func testJsonDecodedArray() {
let jsonData = """
[{"id": \(Locals.userId1), "name": "\(Locals.username1)"}, {"id": \(Locals.userId2), "name": "\(Locals.username2)"}]
""".data(using: .utf8)!
guard let users: [User] = jsonData.jsonDecoded(type: User.self) else {
XCTFail("Error decoding data")
return
}
XCTAssertEqual(users.count, 2)
XCTAssertEqual(users[0].id, Locals.userId1)
XCTAssertEqual(users[0].name, Locals.username1)
XCTAssertEqual(users[1].id, Locals.userId2)
XCTAssertEqual(users[1].name, Locals.username2)
}
func testJsonDecodedWithUserInfo() {
let jsonData = """
{"id": \(Locals.userId1), "name": "\(Locals.username1)"}
""".data(using: .utf8)!
guard let key = CodingUserInfoKey(rawValue: "employer") else {
XCTFail("Error creating user key")
return
}
let userInfo = [key: Locals.companyName]
guard let user: User = jsonData.jsonDecoded(type: User.self, userInfo: userInfo) else {
XCTFail("Error decoding data")
return
}
XCTAssertEqual(user.id, Locals.userId1)
XCTAssertEqual(user.name, Locals.username1)
XCTAssertEqual(user.employer, Locals.companyName)
}
}
@@ -1,75 +0,0 @@
//
// KeychainMock.swift
//
//
// Created by NUT.TECH on 16.02.2023.
//
import Foundation
import WalletFoundation
final class KeychainMock: KeychainProtocol {
private enum KeychainMockLocals {
static let password = "PWD"
static let biometric = "BIO"
}
typealias Value = (value: String, password: String)
private var storage: [String: Value]
init(storage: [String: Value]? = nil) {
self.storage = storage ?? [:]
}
func exist(_ key: Key) -> Bool {
let commonKey = key.with(KeychainMockLocals.password)
return self.storage[commonKey.rawValue].isExist
}
func bioExist(_ key: Key) -> Bool {
let commonKey = key.with(KeychainMockLocals.biometric)
return self.storage[commonKey.rawValue] != nil
}
subscript(biometric key: Key) -> String? {
let commonKey = key.with(KeychainMockLocals.biometric).rawValue
let object = self.storage[commonKey]
return object?.value
}
subscript(key: Key, password password: String) -> String? {
get {
let commonKey = key.with(KeychainMockLocals.password).rawValue
let object = self.storage[commonKey]
return object?.password == password ? object?.value : nil
}
set {
self.update(key, password: password, newValue: newValue)
}
}
private func update(_ key: Key, password: String, newValue: String?) {
if let value = newValue {
self.updateStorage(key: key.with(KeychainMockLocals.password), password: password, value: value)
self.updateStorage(key: key.with(KeychainMockLocals.biometric), password: password, value: value)
} else {
self.updateStorage(key: key.with(KeychainMockLocals.password), password: password, value: nil)
self.updateStorage(key: key.with(KeychainMockLocals.biometric), password: password, value: nil)
}
}
private func updateStorage(key: Key, password: String, value: String?) {
let stringKey = key.rawValue
if self.storage[stringKey] != nil, self.storage[stringKey]?.password == password {
if let value {
self.storage[stringKey]?.value = value
} else {
self.storage.removeValue(forKey: stringKey)
}
} else if let value {
self.storage[stringKey] = Value(value: value, password: password)
}
}
}
@@ -1,103 +0,0 @@
//
// PrivacyKeeperTests.swift
//
//
// Created by user on 16.02.2023.
//
import XCTest
@testable import WalletKit
@testable import WalletFoundation
final class PrivacyKeeperTests: XCTestCase {
func testAES() throws {
let commonKey = "PrivacyKeeper.password"
let encryptedKey = "mdHBVh0lxLT8VBJnL9MYuM/pxWi/QmTCXmW8YZQO8Cs="
let key = "96080AE5042241D9BFF30330C9625201"
let iv = "7C515B5233314B42"
do {
let encryptedCommonKey = try PrivacyKeeper.aesEncrypt(string: commonKey, key: key, iv: iv)
let decryptedCommonKey = try PrivacyKeeper.aesDecrypt(string: encryptedCommonKey, key: key, iv: iv)
XCTAssertEqual(encryptedCommonKey, encryptedKey, "Incorrect encryption result")
XCTAssertEqual(decryptedCommonKey, commonKey, "Incorrect decryption result")
} catch {
XCTFail(error.localizedDescription)
}
}
func testPassword() {
let password = "0000"
let keychainMock = KeychainMock()
let keeper = PrivacyKeeper(keychain: keychainMock)
do {
XCTAssertFalse(keeper.isPasswordExists, "Incorrect isPasswordExists result")
try keeper.update(password: password)
XCTAssertTrue(keeper.isPasswordExists, "Incorrect isPasswordExists result")
XCTAssertTrue(keeper.accept(password: password), "Incorrect password")
let newPassword = "1111"
try keeper.update(password: newPassword, old: password)
XCTAssertTrue(keeper.accept(password: newPassword), "Incorrect password")
let incorrectPassword = "2222"
XCTAssertFalse(keeper.accept(password: incorrectPassword), "Incorrect password check")
keeper.reset()
XCTAssertFalse(keeper.accept(password: newPassword), "Incorrect password reset")
XCTAssertFalse(keeper.isPasswordExists, "Incorrect isPasswordExists result")
} catch {
XCTFail(error.localizedDescription)
}
}
func testPrivateKey() {
let password = "0000"
let privateKey = "privateKey1111"
let commonKey = "accountKey@malinka.privateKey"
let keychainMock = KeychainMock()
let keeper = PrivacyKeeper(keychain: keychainMock)
do {
try keeper.update(password: password)
XCTAssertTrue(keeper.accept(password: password), "Incorrect password")
keeper.update(privateKey: privateKey,
for: commonKey,
password: password)
let readPrivateKey = keeper.privateKey(for: commonKey, password: password)
XCTAssertNotNil(readPrivateKey, "Error setting private key to keychain")
XCTAssertEqual(readPrivateKey, privateKey, "Incorrect private key stored in keychain")
keeper.reset()
let nilPrivateKey = keeper.privateKey(for: commonKey, password: password)
XCTAssertNil(nilPrivateKey, "Incorrect private key reset")
} catch {
XCTFail(error.localizedDescription)
}
}
func testMigration() {
let password = "0000"
let privateKey = "privateKey1111"
let privateKey2 = "privateKey2222"
let migratedCommonKey = "accountName@owner.privateKey"
let keyToMigrate = CommonKey("accountKey2@malinka.privateKey")
let oldKey = CommonKey.key("accountName", suffix: "privateKey")
let newKey = CommonKey.key("accountName@owner", suffix: "privateKey")
let storage = ["accountName.privateKey.PWD": KeychainMock.Value(value: privateKey, password: password),
"PrivacyKeeper.password.PWD": KeychainMock.Value(value: password, password: password),
"accountKey2@malinka.privateKey.PWD": KeychainMock.Value(value: privateKey2, password: password)]
let keychainMock = KeychainMock(storage: storage)
let keeper = PrivacyKeeper(keychain: keychainMock)
keeper.migrate(oldKey: oldKey, newKey: newKey, password: password)
XCTAssertNil(keeper.privateKey(for: oldKey.rawValue, password: password), "Incorrect private key")
XCTAssertEqual(keeper.privateKey(for: migratedCommonKey, password: password), privateKey, "Incorrect private key")
keeper.migrate(oldKey: keyToMigrate, newKey: keyToMigrate, password: password)
XCTAssertEqual(keeper.privateKey(for: keyToMigrate.rawValue, password: password), privateKey2, "Incorrect private key")
}
}
-36
View File
@@ -1,36 +0,0 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "WalletNetwork",
platforms: [.iOS(.v13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "WalletNetwork",
targets: ["WalletNetwork"]),
],
dependencies: [
.package(name: "eosswift", path: "../../Vendors/spm/eos-swift"),
.package(name: "EosioSwift", path: "../Vendors/spm/eosio-swift-1.0.0"),
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.6.2")),
.package(name: "WalletFoundation", path: "../WalletFoundation"),
.package(name: "Mocker", path: "../../Vendors/spm/Mocker-3.0.1")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "WalletNetwork",
dependencies: ["EosioSwift", "eosswift", "Alamofire", "WalletFoundation"],
path: "./Sources"),
.testTarget(
name: "WalletNetworkTests",
dependencies: ["WalletNetwork", "Mocker"],
resources: [
.process("Resources")
])
]
)
@@ -1,52 +0,0 @@
//
// AccountHyperionService.swift
//
//
// Created by Juraldinio on 12/4/22.
//
import Foundation
import Alamofire
import WalletFoundation
public struct AccountHyperionService: NetworkService {
private struct Response: Codable {
let accountNames: [String]
enum CodingKeys: String, CodingKey {
case accountNames = "account_names"
}
}
let environment: NetworkEnvironment
public init(environment: NetworkEnvironment) {
self.environment = environment
}
// Methods
public func fetchNamesHyperion(publicKey: String) async -> [String] {
guard var components = URLComponents(url: self.environment.hyperion.url(), resolvingAgainstBaseURL: false) else {
return []
}
components.path = "/v2/state/get_key_accounts"
components.queryItems = [URLQueryItem(name: "public_key", value: publicKey),
URLQueryItem(name: "skip", value: "0"),
URLQueryItem(name: "limit", value: "5000")]
guard let url = components.url else { return [] }
let response = try? await AF.request(
url,
method: .get,
headers: self.environment.usernames.httpHeaders)
.serializingDecodable(Response.self)
.value
guard let response = response else { return [] }
return response.accountNames
}
}
@@ -1,156 +0,0 @@
//
// AccountService.swift
//
//
// Created by Juraldinio on 01.08.2022.
//
import Foundation
import Alamofire
import WalletFoundation
public struct AccountService: NetworkService {
public enum ServiceError: Error {
case notificationEmptyAccount
case notificationEmptyToken
case notificationEmptyLanguage
}
let environment: NetworkEnvironment
let session: Session
public init(environment: NetworkEnvironment) {
self.environment = environment
if let configuration = environment.configuration {
configuration.headers = HTTPHeaders.default
self.session = Session(configuration: configuration)
} else {
self.session = AF
}
}
// Methods
public func create(username: String, pubKey: String, deviceId: String) async throws -> WalletOrder {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let variables = AccountCreateRequest.Variables(username: username, pubKey: pubKey, deviceId: deviceId)
let result = try? await self.session.request(
self.environment.usernames.url,
method: .post,
parameters: AccountCreateRequest(variables: variables),
encoder: JSONParameterEncoder.default,
headers: self.environment.usernames.httpHeaders
)
.responseString { print(">>>>>> \($0)") }
// .cURLDescription { print($0) }
.serializingDecodable(GraphQLResult<AccountCreateResponse>.self, decoder: decoder)
.value
switch result?.result {
case let .firstType(response): return response.order
case let .secondType(error): throw error.networkServiceError
default: throw NetworkServiceError.uncatched
}
}
public func order(with id: String) async throws -> WalletOrder {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let variables = AccountOrderRequest.Variables(uid: id)
let result = try? await self.session.request(
self.environment.usernames.url,
method: .post,
parameters: AccountOrderRequest(variables: variables),
encoder: JSONParameterEncoder.default,
headers: self.environment.usernames.httpHeaders
)
.responseString { print(">>>>>> \($0)") }
// .cURLDescription { print($0) }
.serializingDecodable(GraphQLResult<AccountOrderResponse>.self, decoder: decoder)
.value
switch result?.result {
case let .firstType(response): return response.order
case let .secondType(error): throw error.networkServiceError
default: throw NetworkServiceError.uncatched
}
}
public enum AvailableType {
case available
case alreadyTaken
case system
}
public func isAvailable(username: String) async -> AvailableType {
guard !username.isEmpty else { return .system }
let variables = AccountCheckNameRequest.Variables(username: username)
let response = try? await self.session.request(
self.environment.usernames.url,
method: .post,
parameters: AccountCheckNameRequest(variables: variables),
encoder: JSONParameterEncoder.default,
headers: self.environment.usernames.httpHeaders)
.responseString { print("checkWalletName: \($0)") }
// .cURLDescription { print($0) }
.serializingDecodable(GraphQLResult<AccountCheckNameResponse>.self)
.value
guard let response = response else { return .system }
switch response.result {
case .firstType: return .available
case let .secondType(type):
switch type {
case let .application(error: errorType) where errorType == "ALREADY_TAKEN":
return .alreadyTaken
default: return .system
}
}
}
public func updateNotification(token: String, accounts: [String], language: String) async throws {
if accounts.isEmpty { throw ServiceError.notificationEmptyAccount }
if token.isEmpty { throw ServiceError.notificationEmptyToken }
if language.isEmpty { throw ServiceError.notificationEmptyLanguage }
let variables = AccountNotificationTokenRequest.Variables(
deviceToken: token,
deviceType: "IOS",
eosAccounts: accounts,
langCode: language,
application: "MALINKA"
)
let result = try? await self.session.request(
self.environment.backend.url,
method: .post,
parameters: AccountNotificationTokenRequest(variables: variables),
encoder: JSONParameterEncoder.default,
headers: self.environment.backend.httpHeaders)
// .responseString { print("updateNotification: \($0)") }
.serializingDecodable(GraphQLResult<AccountNotificationTokenResponse>.self)
.value
print(result ?? "")
}
}
@@ -1,50 +0,0 @@
//
// AccountNodeService.swift
//
//
// Created by Nut.Tech on 16.01.2023.
//
import Alamofire
import Foundation
public struct NodeService: NetworkService {
let environment: NetworkEnvironment
let session: Session
public init(environment: NetworkEnvironment) {
self.environment = environment
if let configuration = environment.configuration {
configuration.headers = HTTPHeaders.default
self.session = Session(configuration: configuration)
} else {
self.session = AF
}
}
public func fetchActions(account: String,
limit: Int,
skip: Int? = nil) async throws -> [NodeAction] {
guard let url = URL(string: "https://eos.greymass.com") else { return [] } //self.environment.hyperion.url()
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return [] }
components.path = "/v1/history/get_actions"
components.queryItems = [URLQueryItem(name: "account_name", value: account),
URLQueryItem(name: "offset", value: "\(-limit)")]
if let skip {
components.queryItems?.append(URLQueryItem(name: "pos", value: "\(skip)"))
}
do {
let result = try await self.session.request(
components,
method: .get
).serializingDecodable(NodeResponse.self)
.value
return result.actions
} catch {
throw error
}
}
}
@@ -1,31 +0,0 @@
//
// AccountCheckNameRequest.swift
//
//
// Created by Juraldinio on 11.08.2022.
//
import Foundation
struct AccountCheckNameRequest: Encodable {
struct Variables: Encodable {
let username: String
}
// MARK: - GraphQL
let variables: Variables
let operationName = "checkWalletName"
let query: String = #"""
query checkWalletName(
$username: String!
) {
checkWalletName(username: $username) { errors }
}
"""#.trimCompact()
}
struct AccountCheckNameResponse: GraphQLResponse {
static let node = "checkWalletName"
}
@@ -1,37 +0,0 @@
//
// AccountCreateRequest.swift
//
//
// Created by Juraldinio on 24.08.2022.
//
import Foundation
struct AccountCreateRequest: Encodable {
struct Variables: Encodable {
let username: String
let pubKey: String
let deviceId: String
}
// MARK: - GraphQL
let variables: Variables
let operationName = "CreateAccount"
let query: String = #"""
mutation CreateAccount($username: String!, $pubKey: String!, $deviceId: String!) {
createAccount(username: $username, pubKey: $pubKey, deviceId: $deviceId) {
errors, order { timeout, id, modified, status }, id
}
}
"""#.trimCompact()
}
struct AccountCreateResponse: GraphQLResponse {
static let node = "createAccount"
let id: Int
let order: WalletOrder
}
@@ -1,45 +0,0 @@
//
// AccountOrderRequest.swift
//
//
// Created by Juraldinio on 29.08.2022.
//
import Foundation
struct AccountOrderRequest: Encodable {
struct Variables: Encodable {
let uid: String
}
// MARK: - GraphQL
let variables: Variables
let operationName = "accountOrder"
let query: String = #"""
query accountOrder(
$uid: String!
) {
accountOrder(uid: $uid) { timeout, id, modified, status, created }
}
"""#.trimCompact()
}
struct AccountOrderResponse: GraphQLResponse {
private enum Common: String, CodingKey {
case order = "accountOrder"
}
static let node = ""
let order: WalletOrder
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Common.self)
self.order = try container.decode(WalletOrder.self, forKey: .order)
}
}
@@ -1,31 +0,0 @@
//
// File.swift
//
//
// Created by Juraldinio on 8/28/22.
//
import Foundation
public struct WalletOrder: Codable {
public enum Status: String, Codable {
/// Order created.
case active = "ACTIVE"
/// Order expired.
case expired = "EXPIRED"
/// Sent to blockchain.
case executed = "EXECUTED"
/// Accepting EOS transaction. Already sent, but not all blocks created.
case creating = "TRX_IN_CHAIN"
/// Failed.
case failed = "FAILED"
/// Created and completed.
case completed = "COMPLETED"
}
public let timeout: Date
public let modified: Date
public let status: Status
public let id: String
}
@@ -1,15 +0,0 @@
//
// NodeAct.swift
//
//
// Created by Nut.Tech on 10.01.2023.
//
import Foundation
public struct NodeAct: Decodable {
public let data: NodeData
public let name: String
public let authorization: [NodeActAuthorization]
public let account: String
}
@@ -1,13 +0,0 @@
//
// NodeActAuthorization.swift
//
//
// Created by Nut.Tech on 10.01.2023.
//
import Foundation
public struct NodeActAuthorization: Codable {
public let actor: String
public let permission: String
}
@@ -1,22 +0,0 @@
//
// NodeAction.swift
//
//
// Created by Nut.Tech on 10.01.2023.
//
import Foundation
public struct NodeAction: Decodable {
public let accountActionSeq: Int
public let actionTrace: NodeActionTrace
public let globalActionSeq: Int
public let irreversible: Bool
enum CodingKeys: String, CodingKey {
case accountActionSeq = "account_action_seq"
case actionTrace = "action_trace"
case globalActionSeq = "global_action_seq"
case irreversible
}
}
@@ -1,24 +0,0 @@
//
// NodeActionTrace.swift
//
//
// Created by Nut.Tech on 11.01.2023.
//
import Foundation
public struct NodeActionTrace: Decodable {
public let act: NodeAct
public let blockNum: Int
public let blockTime: String
public let receiver: String?
public let trxId: String
enum CodingKeys: String, CodingKey {
case blockNum = "block_num"
case blockTime = "block_time"
case trxId = "trx_id"
case receiver
case act
}
}
@@ -1,47 +0,0 @@
//
// NodeData.swift
//
// Created by NUT.TECH on 10.01.2023.
//
import Foundation
public struct NodeData: Decodable {
public let data: [String: Any]
public init(from decoder: Decoder) throws {
// Create a decoding container using DynamicCodingKeys
// The container will contain all the JSON first level key
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
var tempData = [String: Any]()
// Loop through each key
for key in container.allKeys {
if let value = try? container.decode(NodeData.self, forKey: key) {
tempData[key.stringValue] = value.data
} else if let value = try? container.decode(String.self, forKey: key) {
tempData[key.stringValue] = value
} else if let value = try? container.decode(Int.self, forKey: key) {
tempData[key.stringValue] = value
}
}
self.data = tempData
}
private struct DynamicCodingKeys: CodingKey {
// Use for string-keyed dictionary
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
// Use for integer-keyed dictionary
var intValue: Int?
init?(intValue: Int) {
// We are not using this, thus just return nil
return nil
}
}
}
@@ -1,12 +0,0 @@
//
// NodeResponse.swift
//
//
// Created by Nut.Tech on 11.01.2023.
//
import Foundation
public struct NodeResponse: Decodable {
public let actions: [NodeAction]
}
@@ -1,41 +0,0 @@
//
// CommonEnvironment.swift
//
//
// Created by Juraldinio on 04.08.2022.
//
import Foundation
public struct CommonEnvironment: NetworkEnvironment {
public let configuration: URLSessionConfiguration?
public let usernames: NetworkStaticAPIRecord
public let hyperion: NetworkDynamicAPIRecord
public let backend: NetworkStaticAPIRecord
public let node: NetworkDynamicAPIRecord
public init(usernames: NetworkStaticAPIRecord,
backend: NetworkStaticAPIRecord,
hyperion: NetworkDynamicAPIRecord,
node: NetworkDynamicAPIRecord) {
self.configuration = nil
self.usernames = usernames
self.backend = backend
self.hyperion = hyperion
self.node = node
}
public init(usernames: URL,
backend: URL,
hyperion: @escaping NetworkDynamicAPIRecord.Dynamic,
node: @escaping NetworkDynamicAPIRecord.Dynamic,
headers: [String: String] = [:]) {
let userRecord = NetworkStaticAPIRecord(url: usernames, headers: headers)
let backendRecord = NetworkStaticAPIRecord(url: backend, headers: headers)
let hyperionRecord = NetworkDynamicAPIRecord(url: hyperion, headers: headers)
let nodeRecord = NetworkDynamicAPIRecord(url: node, headers: headers)
self.init(usernames: userRecord, backend: backendRecord, hyperion: hyperionRecord, node: nodeRecord)
}
}
@@ -1,106 +0,0 @@
//
// GraphQLResult.swift
//
//
// Created by Juraldinio on 01.08.2022.
//
import Foundation
import WalletFoundation
protocol GraphQLResponse: Decodable {
static var node: String { get }
}
struct GraphQLError: Decodable {
let message: String
}
enum GraphQLResponseError: Decodable {
enum Place: CustomStringConvertible {
case node
case failed
var description: String {
switch self {
case .node: return "node"
case .failed: return "failed"
}
}
}
case system(errors: [GraphQLError])
case application(error: String)
case decode(Place)
init(from decoder: Decoder) throws {
self = .system(errors: [])
}
// MARK: -
var networkServiceError: NetworkServiceError {
switch self {
case let .system(errors: errors): return .gqlSystem(errors.map { $0.message })
case let .application(error: error): return .gqlApplication(error)
case let .decode(place): return .gqlDecode(place.description)
}
}
}
let KEY_ERRORS = "errors"
struct GraphQLResult<Resp: GraphQLResponse>: Decodable {
let result: Either<Resp, GraphQLResponseError>
private enum Common: String, CodingKey {
case data
case errors
}
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Common.self)
// System error catch
if let errors = try? container.decode([GraphQLError].self, forKey: .errors) {
self.result = .secondType(.system(errors: errors))
return
}
// If node is empty
guard !Resp.node.isEmpty else {
if let response = try? container.decode(Resp.self, forKey: .data) {
self.result = .firstType(response)
} else {
self.result = .secondType(.decode(.failed))
}
return
}
let dataContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: .data)
guard let key = DynamicCodingKeys(stringValue: Resp.node) else {
self.result = .secondType(.decode(.node))
return
}
if let responseContainer = try? dataContainer.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: key),
let errorKey = DynamicCodingKeys(stringValue: KEY_ERRORS),
let error = try? responseContainer.decode(String.self, forKey: errorKey) {
self.result = .secondType(.application(error: error))
} else if let response = try? dataContainer.decode(Resp.self, forKey: key) {
self.result = .firstType(response)
} else {
self.result = .secondType(.decode(.failed))
}
}
}
@@ -1,84 +0,0 @@
//
// NetworkService.swift
//
//
// Created by Juraldinio on 04.08.2022.
//
import Foundation
import Alamofire
public struct NetworkStaticAPIRecord {
public typealias Headers = [String: String]
public let url: URL
public let headers: Headers?
public init(url: URL, headers: Headers?) {
self.url = url
self.headers = headers
}
var httpHeaders: HTTPHeaders? {
guard let headers = self.headers else { return nil }
return HTTPHeaders(headers.compactMap { key, value in HTTPHeader(name: key, value: value) })
}
}
public struct NetworkDynamicAPIRecord {
public typealias Headers = [String: String]
public typealias Dynamic = () -> URL
public let url: Dynamic
public let headers: Headers?
public init(url: @escaping Dynamic, headers: Headers?) {
self.url = url
self.headers = headers
}
var httpHeaders: HTTPHeaders? {
guard let headers = self.headers else { return nil }
return HTTPHeaders(headers.compactMap { key, value in HTTPHeader(name: key, value: value) })
}
}
public protocol NetworkEnvironment {
var configuration: URLSessionConfiguration? { get }
var usernames: NetworkStaticAPIRecord { get }
var backend: NetworkStaticAPIRecord { get }
var hyperion: NetworkDynamicAPIRecord { get }
var node: NetworkDynamicAPIRecord { get }
func isEquals(other: NetworkEnvironment) -> Bool
}
public protocol NetworkService {
init(environment: NetworkEnvironment)
}
public enum NetworkServiceError: Error {
case invalidUrl
case gqlSystem([String])
case gqlApplication(String)
case gqlDecode(String)
case uncatched
}
// MARK: - Equatable
extension NetworkEnvironment {
public func isEquals(other: NetworkEnvironment) -> Bool {
return self.configuration == other.configuration &&
self.usernames.url == other.usernames.url &&
self.usernames.headers == other.usernames.headers &&
self.backend.url == other.backend.url &&
self.backend.headers == other.backend.headers &&
self.hyperion.url() == other.hyperion.url() &&
self.hyperion.headers == other.hyperion.headers &&
self.node.url() == other.node.url() &&
self.node.headers == other.node.headers
}
}
@@ -1,97 +0,0 @@
//
// DeviceService.swift
//
//
// Created by Juraldinio on 01.08.2022.
//
import Foundation
import Alamofire
public struct DeviceService: NetworkService {
let environment: NetworkEnvironment
let session: Session
public init(environment: NetworkEnvironment) {
self.environment = environment
if let configuration = environment.configuration {
configuration.headers = HTTPHeaders.default
self.session = Session(configuration: configuration)
} else {
self.session = AF
}
}
public func createDevice(uuid: String) async throws -> Device {
let variables = RegisterDeviceRequest.Variables(cid: uuid)
let result = try? await self.session.request(
self.environment.usernames.url,
method: .post,
parameters: RegisterDeviceRequest(variables: variables),
encoder: JSONParameterEncoder.default,
headers: self.environment.usernames.httpHeaders
)
// .responseString { print($0) }
// .cURLDescription { print($0) }
.serializingDecodable(GraphQLResult<RegisterDeviceResponse>.self)
.value
switch result?.result {
case let .firstType(device): return Device(uuid: uuid, id: device.uid, isTrusted: device.isTrustedNow)
case let .secondType(error): throw error.networkServiceError
default: throw NetworkServiceError.uncatched
}
}
public func stateDevice(id: String) async throws -> StateDevice {
let variables = StateDeviceRequest.Variables(uid: id)
let result = try? await self.session.request(
self.environment.usernames.url,
method: .post,
parameters: StateDeviceRequest(variables: variables),
encoder: JSONParameterEncoder.default,
headers: self.environment.usernames.httpHeaders
)
// .responseString { print(">>>>>> \($0)") }
// .cURLDescription { print($0) }
.serializingDecodable(GraphQLResult<StateDeviceResponse>.self)
.value
switch result?.result {
case let .firstType(state): return StateDevice(availableAccounts: state.availableAccounts,
isTrusted: state.isTrustedNow)
case let .secondType(error): throw error.networkServiceError
default: throw NetworkServiceError.uncatched
}
}
public func checkDevice(id: String, token: String) async throws -> DeviceStatus {
let variables = CheckDeviceRequest.Variables(uid: id, token: token)
let result = try? await self.session.request(
self.environment.usernames.url,
method: .post,
parameters: CheckDeviceRequest(variables: variables),
encoder: JSONParameterEncoder.default,
headers: self.environment.usernames.httpHeaders
)
// .responseString { print($0) }
// .cURLDescription { print($0) }
.serializingDecodable(GraphQLResult<CheckDeviceResponse>.self)
.value
switch result?.result {
case let .firstType(status): return status.status
case let .secondType(error): throw error.networkServiceError
default: throw NetworkServiceError.uncatched
}
}
}
@@ -1,35 +0,0 @@
//
// CheckDeviceRequest.swift
//
//
// Created by Juraldinio on 24.08.2022.
//
import Foundation
struct CheckDeviceRequest: Encodable {
struct Variables: Encodable {
let uid: String
let token: String
}
// MARK: - GraphQL
let operationName = "CheckDevice"
let query: String = #"""
mutation CheckDevice($uid: String!, $token: String!) {
checkDevice(uid: $uid, token: $token) {
status
}
}
"""#.trimCompact()
let variables: Variables
}
struct CheckDeviceResponse: GraphQLResponse {
static let node = "checkDevice"
let status: DeviceStatus
}
@@ -1,36 +0,0 @@
//
// RegisterDeviceRequest.swift
//
//
// Created by Juraldinio on 04.08.2022.
//
import Foundation
struct RegisterDeviceRequest: Encodable {
struct Variables: Encodable {
let cid: String
}
// MARK: - GraphQL
let operationName = "RegisterDevice"
let query: String = #"""
mutation RegisterDevice($cid: String) {
registerDevice(kind: IOS, cid: $cid) {
isTrustedNow
uid
}
}
"""#.trimCompact()
let variables: Variables
}
struct RegisterDeviceResponse: GraphQLResponse {
static let node = "registerDevice"
let isTrustedNow: Bool
let uid: String
}
@@ -1,34 +0,0 @@
//
// StateDeviceRequest.swift
//
//
// Created by Juraldinio on 29.08.2022.
//
import Foundation
struct StateDeviceRequest: Encodable {
struct Variables: Encodable {
let uid: String
}
// MARK: - GraphQL
let variables: Variables
let operationName = "device"
let query: String = #"""
query device(
$uid: String!
) {
device(uid: $uid) { availableAccounts, isTrustedNow }
}
"""#.trimCompact()
}
struct StateDeviceResponse: GraphQLResponse {
static let node = "device"
let availableAccounts: Int
let isTrustedNow: Bool
}
@@ -1,18 +0,0 @@
//
// Device.swift
//
//
// Created by Juraldinio on 01.08.2022.
//
import Foundation
/// Model represent device
public struct Device {
/// Device generated Identity UUID
public let uuid: String
/// Server generated Identity
public let id: String
/// Is device trusted
public let isTrusted: Bool
}
@@ -1,14 +0,0 @@
//
// DeviceStatus.swift
//
//
// Created by Juraldinio on 23.08.2022.
//
import Foundation
public enum DeviceStatus: String, Decodable {
case valid = "Valid"
case invalid = "Invalid"
case unexpected = "UnexpectedError"
}
@@ -1,13 +0,0 @@
//
// StateDevice.swift
//
//
// Created by Juraldinio on 29.08.2022.
//
import Foundation
public struct StateDevice {
public let availableAccounts: Int
public let isTrusted: Bool
}
@@ -1,68 +0,0 @@
//
// EOSService.swift
//
//
// Created by Juraldinio on 31.07.2022.
//
import Foundation
import Alamofire
import WalletFoundation
import EosioSwift
import eosswift
public struct EOSService: NetworkService {
let environment: NetworkEnvironment
public init(environment: NetworkEnvironment) {
self.environment = environment
}
// Methods
public func requireNodes() async -> [String] {
let result = try? await AF.request(
self.environment.backend.url,
method: .post,
parameters: EOSNodeRequest(),
encoder: JSONParameterEncoder.default,
headers: self.environment.backend.httpHeaders)
// .responseString(completionHandler: { print(">>>~~~>>> \($0)") })
.serializingDecodable(GraphQLResult<EOSNodesResponse>.self)
.value
switch result?.result {
case let .firstType(value):
return value.nodes.map { $0.url }
default:
return []
}
}
public func requireHyperions() async -> [String] {
let result = try? await AF.request(
self.environment.backend.url,
method: .post,
parameters: EOSHyperionsRequest(),
encoder: JSONParameterEncoder.default,
headers: self.environment.backend.httpHeaders)
.serializingDecodable(GraphQLResult<EOSHyperionsResponse>.self)
.value
switch result?.result {
case let .firstType(value):
return value.hyperions.map { $0.url }
default:
return []
}
}
public func getAccount(_ account: String, completion: @escaping (EosioResult<EosioRpcAccountResponse, EosioError>) -> Void) {
EosioRpcProvider(endpoint: self.environment.node.url(), headers: self.environment.node.headers)
.getAccount(requestParameters: EosioRpcAccountRequest(accountName: account), completion: completion)
}
}
@@ -1,35 +0,0 @@
//
// EOSHyperionsRequest.swift
//
//
// Created by NUT.Tech on 01.08.2022.
//
import Foundation
struct EOSHyperionsRequest: Encodable {
// MARK: - GraphQL
let query: String = #"""
query {
getEOSHyperions {
hyperions {
url
}
}
}
"""#.trimCompact()
}
struct EOSHyperionsResponse: GraphQLResponse {
static let node = "getEOSHyperions"
struct Hyperion: Decodable {
let url: String
}
let hyperions: [Hyperion]
}
@@ -1,37 +0,0 @@
//
// EOSNodesQueryRequest.swift
//
//
// Created by Juraldinio on 01.08.2022.
//
import Foundation
struct EOSNodeRequest: Encodable {
// MARK: - GraphQL
let operationName = "getEOSNodes"
let query: String = #"""
query getEOSNodes {
getEOSNodes {
nodes {
url
}
}
}
"""#.trimCompact()
let variables: [String: String]? = nil
}
struct EOSNodesResponse: GraphQLResponse {
static let node = "getEOSNodes"
struct Node: Decodable {
let url: String
}
let nodes: [Node]
}
@@ -1,311 +0,0 @@
//
// DeviceServiceTests.swift
//
//
// Created by Juraldinio on 13.01.2023.
//
import Foundation
import XCTest
import WalletFoundation
@testable
import WalletNetwork
import Mocker
final class DeviceServiceTests: XCTestCase {
override func tearDown() {
Mocker.removeAll()
}
// MARK: - CreateDevice
// When receive incorrect status we must throw uncatched exception.
func testCreateDeviceFailUncatched() async {
let url = URL(string: "https://malinka.life/create")!
let uuid = "1234-0987-4567"
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubUsernames(configuration: configuration, url: url)
let service = DeviceService(environment: env)
Mock(url: url,
dataType: .json,
statusCode: 500,
data: [ .post: Data() ]
).register()
do {
_ = try await service.createDevice(uuid: uuid)
XCTFail("Require to fail")
} catch NetworkServiceError.uncatched {
XCTAssertTrue(true)
} catch {
XCTFail("Require '\(NetworkServiceError.uncatched)' exception")
}
}
// When we receive incorrect data that we can't decode.
func testCreateDeviceFailDecode() async {
let url = URL(string: "https://malinka.life/create")!
let uuid = "1234-0987-4567"
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubUsernames(configuration: configuration, url: url)
let service = DeviceService(environment: env)
Mock(url: url,
dataType: .json,
statusCode: 200,
data: [ .post: DeviceServiceData.createDeviceDecodeFailed ]
).register()
do {
_ = try await service.createDevice(uuid: uuid)
} catch NetworkServiceError.gqlDecode(let message) {
XCTAssertEqual(message, "failed")
} catch {
XCTFail("Catch exceprion '\(error)' but require NetworkServiceError.gqlDecode")
}
}
// When we successed create device.
func testCreateDeviceSuccessTrusted() async {
let url = URL(string: "https://malinka.life/create")!
let uuid = "1234-0987-4567"
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubUsernames(configuration: configuration, url: url)
let service = DeviceService(environment: env)
Mock(url: url,
dataType: .json,
statusCode: 200,
data: [ .post: DeviceServiceData.createDeviceTrusted ]
).register()
do {
let device = try await service.createDevice(uuid: uuid)
XCTAssertTrue(device.isTrusted)
XCTAssertEqual(device.id, "a88c4532a09024c245e9")
XCTAssertEqual(device.uuid, uuid)
} catch {
XCTFail("Catch exceprion \(error)")
}
}
// Test send correct request.
func testCreateDeviceSuccessRequest() async {
let url = URL(string: "https://malinka.life/query")!
let uuid = "1234-0987-4567"
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubUsernames(configuration: configuration, url: url)
let service = DeviceService(environment: env)
let requestExpectation = XCTestExpectation(description: "Request")
let completionExpectation = XCTestExpectation(description: "Completion")
var mock = Mock(url: url, dataType: .json, statusCode: 200, data: [.post: Data()])
mock.onRequestHandler = OnRequestHandler(httpBodyType: [String: Either<String, [String: String]>].self) { request, object in
XCTAssertEqual(url, mock.request.url)
XCTAssertEqual(request.method, .post)
XCTAssertNotNil(object)
let registerDevice = RegisterDeviceRequest(variables: RegisterDeviceRequest.Variables(cid: uuid))
XCTAssertNotNil(object!["query"])
object!["query"]!.map(firstTypeTransform: {
XCTAssertEqual($0, registerDevice.query)
}, secondTypeTransform: {
XCTFail("Require 'String' type but receive \($0)")
})
XCTAssertNotNil(object!["operationName"])
object!["operationName"]!.map(firstTypeTransform: {
XCTAssertEqual($0, registerDevice.operationName)
}, secondTypeTransform: {
XCTFail("Require 'String' type but receive \($0)")
})
XCTAssertNotNil(object!["variables"])
object!["variables"]!.map(firstTypeTransform: {
XCTFail("Require '[String: String]' type but receive \($0)")
}, secondTypeTransform: {
XCTAssertNotNil($0["cid"])
XCTAssertEqual($0["cid"], registerDevice.variables.cid)
})
requestExpectation.fulfill()
}
mock.completion = {
completionExpectation.fulfill()
}
mock.register()
_ = try? await service.createDevice(uuid: uuid)
wait(for: [requestExpectation, completionExpectation], timeout: 2.0)
}
// MARK: - StateDevice
// Receive null values in fields - we must throw exception.
func testQueryStateDeviceException() async {
let url = URL(string: "https://malinka.life/query")!
let id = "1234-0987-4567"
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubUsernames(configuration: configuration, url: url)
let service = DeviceService(environment: env)
Mock(url: url,
dataType: .json,
statusCode: 200,
data: [ .post: DeviceServiceData.queryDeviceStateNull ]
).register()
do {
_ = try await service.stateDevice(id: id)
XCTFail("Require catch exceprion NetworkServiceError.gqlDecode")
} catch {
XCTAssertTrue(true)
}
}
// Receive untrusted status.
func testQueryStateDeviceUntrusted() async {
let url = URL(string: "https://malinka.life/query")!
let id = "1234-0987-4567"
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubUsernames(configuration: configuration, url: url)
let service = DeviceService(environment: env)
Mock(url: url,
dataType: .json,
statusCode: 200,
data: [ .post: DeviceServiceData.queryDeviceStateUntrusted ]
).register()
do {
let state = try await service.stateDevice(id: id)
XCTAssertFalse(state.isTrusted)
XCTAssertEqual(state.availableAccounts, 0)
} catch {
XCTFail("Catch exceprion \(error)")
}
}
// Receive trusted status.
func testQueryStateDeviceTrusted() async {
let url = URL(string: "https://malinka.life/query")!
let id = "1234-0987-4567"
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubUsernames(configuration: configuration, url: url)
let service = DeviceService(environment: env)
Mock(url: url,
dataType: .json,
statusCode: 200,
data: [ .post: DeviceServiceData.queryDeviceStateTrusted ]
).register()
do {
let state = try await service.stateDevice(id: id)
XCTAssertTrue(state.isTrusted)
XCTAssertEqual(state.availableAccounts, 2)
} catch {
XCTFail("Catch exceprion \(error)")
}
}
// Check sent correct request.
func testQueryStateDeviceRequest() async {
let url = URL(string: "https://malinka.life/query")!
let id = "1234-0987-4567"
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubUsernames(configuration: configuration, url: url)
let service = DeviceService(environment: env)
let requestExpectation = XCTestExpectation(description: "Request")
let completionExpectation = XCTestExpectation(description: "Completion")
var mock = Mock(url: url, dataType: .json, statusCode: 200, data: [.post: Data()])
mock.onRequestHandler = OnRequestHandler(httpBodyType: [String: Either<String, [String: String]>].self) { request, object in
XCTAssertEqual(url, mock.request.url)
XCTAssertEqual(request.method, .post)
XCTAssertNotNil(object)
let stateRequest = StateDeviceRequest(variables: StateDeviceRequest.Variables(uid: id))
XCTAssertNotNil(object!["query"])
object!["query"]!.map(firstTypeTransform: {
XCTAssertEqual($0, stateRequest.query)
}, secondTypeTransform: {
XCTFail("Require 'String' type but receive \($0)")
})
XCTAssertNotNil(object!["operationName"])
object!["operationName"]!.map(firstTypeTransform: {
XCTAssertEqual($0, stateRequest.operationName)
}, secondTypeTransform: {
XCTFail("Require 'String' type but receive \($0)")
})
XCTAssertNotNil(object!["variables"])
object!["variables"]!.map(firstTypeTransform: {
XCTFail("Require '[String: String]' type but receive \($0)")
}, secondTypeTransform: {
XCTAssertNotNil($0["uid"])
XCTAssertEqual($0["uid"], stateRequest.variables.uid)
})
requestExpectation.fulfill()
}
mock.completion = {
completionExpectation.fulfill()
}
mock.register()
_ = try? await service.stateDevice(id: id)
wait(for: [requestExpectation, completionExpectation], timeout: 2.0)
}
// MARK: - CheckDevice
}
@@ -1,28 +0,0 @@
//
// DeviceServiceData.swift
//
//
// Created by Juraldinio on 13.01.2023.
//
import Foundation
final class DeviceServiceData {
static let createDeviceTrusted = try! Data(contentsOf: Bundle.module.url(forResource: "CreateDeviceSuccessTrusted",
withExtension: "json")!)
static let createDeviceDecodeFailed = try! Data(contentsOf: Bundle.module.url(forResource: "CreateDeviceSuccessNull",
withExtension: "json")!)
static let queryDeviceStateNull = try! Data(contentsOf: Bundle.module.url(forResource: "QueryDeviceSuccessNull",
withExtension: "json")!)
static let queryDeviceStateUntrusted = try! Data(contentsOf: Bundle.module.url(forResource: "QueryDeviceSuccessUntrusted",
withExtension: "json")!)
static let queryDeviceStateTrusted = try! Data(contentsOf: Bundle.module.url(forResource: "QueryDeviceSuccessTrusted",
withExtension: "json")!)
static let nodeActionsExample = try! Data(contentsOf: Bundle.module.url(forResource: "v1historyTestmalinka1",
withExtension: "json")!)
}
@@ -1,44 +0,0 @@
//
// NetworkEnvironmentImpl.swift
//
//
// Created by Juraldinio on 13.01.2023.
//
import Foundation
import WalletNetwork
struct NetworkEnvironmentImpl: NetworkEnvironment {
let configuration: URLSessionConfiguration?
let usernames: NetworkStaticAPIRecord
let backend: NetworkStaticAPIRecord
let hyperion: NetworkDynamicAPIRecord
let node: NetworkDynamicAPIRecord
static func createStubUsernames(configuration: URLSessionConfiguration,
url: URL,
headers: NetworkStaticAPIRecord.Headers? = nil) -> NetworkEnvironment {
let record = NetworkStaticAPIRecord(url: url, headers: headers)
let dumpUrl = URL(string: "Empty")!
return NetworkEnvironmentImpl(configuration: configuration,
usernames: record,
backend: NetworkStaticAPIRecord(url: dumpUrl, headers: nil),
hyperion: NetworkDynamicAPIRecord(url: { dumpUrl }, headers: nil),
node: NetworkDynamicAPIRecord(url: { dumpUrl }, headers: nil))
}
static func createStubNodes(configuration: URLSessionConfiguration,
url: URL,
headers: NetworkStaticAPIRecord.Headers? = nil) -> NetworkEnvironment {
let record = NetworkDynamicAPIRecord(url: { url }, headers: headers)
let dumpUrl = URL(string: "Empty")!
return NetworkEnvironmentImpl(configuration: configuration,
usernames: NetworkStaticAPIRecord(url: dumpUrl, headers: nil),
backend: NetworkStaticAPIRecord(url: dumpUrl, headers: nil),
hyperion: NetworkDynamicAPIRecord(url: { dumpUrl }, headers: nil),
node: record)
}
}
@@ -1,120 +0,0 @@
//
// NodeActionModelsTests.swift
//
//
// Created by Nut.Tech on 13.01.2023.
//
import WalletNetwork
import Foundation
import XCTest
final class NodeActionModelsTests: XCTestCase {
func testNodeActAuthorization() {
let data = """
{
"actor": "testmalinka1",
"permission": "owner"
}
""".data(using: .utf8)!
let nodeAct = try? JSONDecoder().decode(NodeActAuthorization.self, from: data)
XCTAssertNotNil(nodeAct, "Error parsing NodeActAuthorization object")
XCTAssertEqual(nodeAct?.actor, "testmalinka1", "Error parsing NodeActAuthorization.actor value")
XCTAssertEqual(nodeAct?.permission, "owner", "Error parsing NodeActAuthorization.owner value")
}
func testNodeData() {
let data = """
{
"from": "testmalinka1",
"memo": "buyram:testmalinka1",
"quantity": "0.1000 EOS",
"to": "malinkawallt"
}
""".data(using: .utf8)!
let nodeData = try? JSONDecoder().decode(NodeData.self, from: data)
XCTAssertNotNil(nodeData, "Error parsing NodeData object")
guard let parsedData = nodeData?.data else {
XCTFail("Error parsing data value")
return
}
XCTAssertEqual(parsedData["from"] as? String, "testmalinka1", "Error parsing data value")
XCTAssertEqual(parsedData["memo"] as? String, "buyram:testmalinka1", "Error parsing data value")
XCTAssertEqual(parsedData["quantity"] as? String, "0.1000 EOS", "Error parsing data value")
XCTAssertEqual(parsedData["to"] as? String, "malinkawallt", "Error parsing data value")
}
func testNodeAct() {
let data = """
{
"account": "eosio.token",
"authorization": [
{
"actor": "testmalinka1",
"permission": "owner"
}
],
"data": {
"from": "testmalinka1",
"memo": "buyram:testmalinka1",
"quantity": "0.1000 EOS",
"to": "malinkawallt"
},
"hex_data": "100c9c2e1a99b1ca906334dcc0e9a291e80300000000000004454f53000000001362757972616d3a746573746d616c696e6b6131",
"name": "transfer"
}
""".data(using: .utf8)!
let nodeAct = try? JSONDecoder().decode(NodeAct.self, from: data)
XCTAssertNotNil(nodeAct, "Error parsing NodeAct object")
XCTAssertNotNil(nodeAct?.data, "Error parsing NodeAct.data object")
XCTAssertNotNil(nodeAct?.authorization, "buyram:testmalinka1", file: "Error parsing NodeAct.authorization objects")
XCTAssertEqual(nodeAct?.authorization.count, 1, "Error parsing NodeAct.authorization objects")
XCTAssertEqual(nodeAct?.name, "transfer", "Error parsing NodeAct.name value")
XCTAssertEqual(nodeAct?.account, "eosio.token", "Error parsing NodeAct.account value")
}
func testNodeActionTrace() {
guard let url = Bundle.module.url(forResource: "NodeActionTrace", withExtension: "json"),
let data = try? Data(contentsOf: url),
let result = try? JSONDecoder().decode(NodeActionTrace.self, from: data)
else {
XCTFail("Error reading JSON file")
return
}
XCTAssertNotNil(result.act, "Error parsing NodeActionTrace.act object")
XCTAssertEqual(result.blockNum, 288713481, "Error parsing NodeActionTrace.blockNum value")
XCTAssertEqual(result.blockTime, "2023-01-12T15:27:01.500", "Error parsing NodeActionTrace.blockTime value")
XCTAssertEqual(result.receiver, "malinkawallt", "Error parsing NodeActionTrace.receiver value")
XCTAssertEqual(result.trxId, "233cb97fcc9a6c8cc7943e021cd9705dd5ff7b82b1fa3a93afaf240f35f2c31c", "Error parsing NodeActionTrace.trxId value")
}
func testNodeAction() {
guard let url = Bundle.module.url(forResource: "NodeAction", withExtension: "json"),
let data = try? Data(contentsOf: url),
let result = try? JSONDecoder().decode(NodeAction.self, from: data)
else {
XCTFail("Error reading JSON file")
return
}
XCTAssertNotNil(result.actionTrace, "Error parsing NodeAction.actionTrace value")
XCTAssertEqual(result.accountActionSeq, 2858, "Error parsing NodeAction.accountActionSeq value")
XCTAssertEqual(result.globalActionSeq, 357585823156, "Error parsing NodeAction.globalActionSeq value")
XCTAssertTrue(result.irreversible, "Error parsing NodeAction.irreversible value")
}
func testNodeResponse() {
guard let url = Bundle.module.url(forResource: "NodeResponse", withExtension: "json"),
let data = try? Data(contentsOf: url),
let result = try? JSONDecoder().decode(NodeResponse.self, from: data)
else {
XCTFail("Error reading JSON file")
return
}
XCTAssertNotNil(result.actions, "Error parsing NodeResponse")
XCTAssertEqual(result.actions.count, 1, "Error parsing NodeResponse.actions")
}
}
@@ -1,68 +0,0 @@
//
// NodeServiceTests.swift
//
//
// Created by Nut.Tech on 16.01.2023.
//
import XCTest
@testable import WalletNetwork
import Mocker
final class NodeServiceTests: XCTestCase {
override func tearDown() {
Mocker.removeAll()
}
func testFetchNodeActions() async {
let accountName = "testmalinka3"
let offset = -100
let url = URL(string: "https://eos.greymass.com/v1/history/get_actions?account_name=\(accountName)&offset=\(offset)")!
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let env = NetworkEnvironmentImpl.createStubNodes(configuration: configuration, url: url)
let service = NodeService(environment: env)
Mock(url: url,
ignoreQuery: true,
dataType: .json,
statusCode: 200,
data: [ .get: DeviceServiceData.nodeActionsExample ]
).register()
do {
let state = try await service.fetchActions(account: accountName,
limit: offset)
guard let firstAction = state.first else {
XCTFail("Error parsing NodeActions")
return
}
XCTAssertEqual(state.count, 100, "Incorrect number of node actions")
XCTAssertEqual(firstAction.accountActionSeq, 2759, "Error parsing NodeAction.accountActionSeq")
XCTAssertEqual(firstAction.globalActionSeq, 357563493447, "Error parsing NodeAction.globalActionSeq")
XCTAssertTrue(firstAction.irreversible, "Error parsing NodeAction.irreversible")
XCTAssertNotNil(firstAction.actionTrace, "Error parsing NodeAction.actionTrace")
XCTAssertEqual(firstAction.actionTrace.blockTime, "2023-01-09T13:55:06.000", "Error parsing NodeAction.actionTrace.blockTime")
XCTAssertEqual(firstAction.actionTrace.blockNum, 288184258, "Error parsing NodeAction.actionTrace.blockNum")
XCTAssertEqual(firstAction.actionTrace.trxId, "a4dfd44c82cf0a3be1930bf6e4ec6ea51c1b331a4e77864a0d2cae4d06e8683d", "Error parsing NodeAction.actionTrace.trxId")
XCTAssertEqual(firstAction.actionTrace.receiver, "testmalinka1", "Error parsing NodeAction.actionTrace.receiver")
let act = firstAction.actionTrace.act
XCTAssertEqual(act.account, "eosio.token", "Error parsing NodeActionTrace.act.account")
XCTAssertEqual(act.name, "transfer", "Error parsing NodeActionTrace.act.name")
XCTAssertEqual(act.authorization.count, 1, "Error parsing NodeActionTrace.act.authorization")
XCTAssertNotNil(act.data, "Error parsing NodeActionTrace.act.data")
guard let authorization = act.authorization.first else {
XCTFail("Error parsing NodeAction.actionTrace.act.authorization")
return
}
XCTAssertEqual(authorization.permission, "owner", "Error parsing NodeActionTrace.act.authorization.permission")
XCTAssertEqual(authorization.actor, "testmalinka1", "Error parsing NodeActionTrace.act.authorization.actor")
} catch {
XCTFail("Catch exceprion \(error)")
}
}
}
@@ -1 +0,0 @@
{"data": {"registerDevice": {"isTrustedNow": false, "uid": null}}}
@@ -1 +0,0 @@
{"data": {"registerDevice": {"isTrustedNow": true, "uid": "a88c4532a09024c245e9"}}}
@@ -1,51 +0,0 @@
{
"account_action_seq": 2858,
"action_trace": {
"account_ram_deltas": [],
"act": {
"account": "eosio.token",
"authorization": [
{
"actor": "testmalinka1",
"permission": "owner"
}
],
"data": {
"from": "testmalinka1",
"memo": "buyram:testmalinka1",
"quantity": "0.1000 EOS",
"to": "malinkawallt"
},
"hex_data": "100c9c2e1a99b1ca906334dcc0e9a291e80300000000000004454f53000000001362757972616d3a746573746d616c696e6b6131",
"name": "transfer"
},
"action_ordinal": 4,
"block_num": 288713481,
"block_time": "2023-01-12T15:27:01.500",
"closest_unnotified_ancestor_action_ordinal": 2,
"context_free": false,
"creator_action_ordinal": 2,
"elapsed": 2,
"producer_block_id": "11356b09099ac39c311d160b99824c40825a5b65f7ddc75fb1e4a5d4c57cde68",
"receipt": {
"abi_sequence": 4,
"act_digest": "0d8c0d9d769f6b68d2b3b2cc1dd2b219eb1ddd7d52c83d221abd74541f6f687b",
"auth_sequence": [
[
"testmalinka1",
2192
]
],
"code_sequence": 4,
"global_sequence": 357585823156,
"receiver": "malinkawallt",
"recv_sequence": 14905
},
"receiver": "malinkawallt",
"trx_id": "233cb97fcc9a6c8cc7943e021cd9705dd5ff7b82b1fa3a93afaf240f35f2c31c"
},
"block_num": 288713481,
"block_time": "2023-01-12T15:27:01.500",
"global_action_seq": 357585823156,
"irreversible": true
}
@@ -1,44 +0,0 @@
{
"account_ram_deltas": [],
"act": {
"account": "eosio.token",
"authorization": [
{
"actor": "testmalinka1",
"permission": "owner"
}
],
"data": {
"from": "testmalinka1",
"memo": "buyram:testmalinka1",
"quantity": "0.1000 EOS",
"to": "malinkawallt"
},
"hex_data": "100c9c2e1a99b1ca906334dcc0e9a291e80300000000000004454f53000000001362757972616d3a746573746d616c696e6b6131",
"name": "transfer"
},
"action_ordinal": 4,
"block_num": 288713481,
"block_time": "2023-01-12T15:27:01.500",
"closest_unnotified_ancestor_action_ordinal": 2,
"context_free": false,
"creator_action_ordinal": 2,
"elapsed": 2,
"producer_block_id": "11356b09099ac39c311d160b99824c40825a5b65f7ddc75fb1e4a5d4c57cde68",
"receipt": {
"abi_sequence": 4,
"act_digest": "0d8c0d9d769f6b68d2b3b2cc1dd2b219eb1ddd7d52c83d221abd74541f6f687b",
"auth_sequence": [
[
"testmalinka1",
2192
]
],
"code_sequence": 4,
"global_sequence": 357585823156,
"receiver": "malinkawallt",
"recv_sequence": 14905
},
"receiver": "malinkawallt",
"trx_id": "233cb97fcc9a6c8cc7943e021cd9705dd5ff7b82b1fa3a93afaf240f35f2c31c"
}
@@ -1,57 +0,0 @@
{
"actions": [
{
"account_action_seq": 2858,
"action_trace": {
"account_ram_deltas": [],
"act": {
"account": "eosio.token",
"authorization": [
{
"actor": "testmalinka1",
"permission": "owner"
}
],
"data": {
"from": "testmalinka1",
"memo": "buyram:testmalinka1",
"quantity": "0.1000 EOS",
"to": "malinkawallt"
},
"hex_data": "100c9c2e1a99b1ca906334dcc0e9a291e80300000000000004454f53000000001362757972616d3a746573746d616c696e6b6131",
"name": "transfer"
},
"action_ordinal": 4,
"block_num": 288713481,
"block_time": "2023-01-12T15:27:01.500",
"closest_unnotified_ancestor_action_ordinal": 2,
"context_free": false,
"creator_action_ordinal": 2,
"elapsed": 2,
"producer_block_id": "11356b09099ac39c311d160b99824c40825a5b65f7ddc75fb1e4a5d4c57cde68",
"receipt": {
"abi_sequence": 4,
"act_digest": "0d8c0d9d769f6b68d2b3b2cc1dd2b219eb1ddd7d52c83d221abd74541f6f687b",
"auth_sequence": [
[
"testmalinka1",
2192
]
],
"code_sequence": 4,
"global_sequence": 357585823156,
"receiver": "malinkawallt",
"recv_sequence": 14905
},
"receiver": "malinkawallt",
"trx_id": "233cb97fcc9a6c8cc7943e021cd9705dd5ff7b82b1fa3a93afaf240f35f2c31c"
},
"block_num": 288713481,
"block_time": "2023-01-12T15:27:01.500",
"global_action_seq": 357585823156,
"irreversible": true
}
],
"head_block_num": 288838483,
"last_irreversible_block": 288838158
}
@@ -1,8 +0,0 @@
{
"data": {
"device": {
"availableAccounts": null,
"isTrustedNow": null
}
}
}
@@ -1,8 +0,0 @@
{
"data": {
"device": {
"availableAccounts": 2,
"isTrustedNow": true
}
}
}
@@ -1,8 +0,0 @@
{
"data": {
"device": {
"availableAccounts": 0,
"isTrustedNow": false
}
}
}
-30
View File
@@ -1,30 +0,0 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "WalletUIComponents",
platforms: [.iOS(.v13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "WalletUIComponents",
targets: ["WalletUIComponents"]),
],
dependencies: [ ],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "WalletUIComponents",
dependencies: [ ],
path: "./Sources"),
.testTarget(
name: "WalletUIComponentsTests",
dependencies: ["WalletUIComponents"],
path: "Tests"//, // Test files
// resources: [.copy("TestData")] // The test data files, copy files without modifying them
)
]
)
@@ -1,59 +0,0 @@
//
// TextField.swift
// Malinka
//
// Created by NUT.Tech on 08.02.2023.
// Copyright © 2023 NUT.Tech. All rights reserved.
//
import SwiftUI
import Combine
/// Struct can be replaced to SwiftUI's native TextField after iOS 15.0.
/// It was created for access to firstResponder logic which has no analogue
/// until iOS 15.0 with @FocusState wrapper type and isFocused property of TextField.
public struct TextField: UIViewRepresentable {
@ObservedObject
public var viewModel: TextFieldViewModel
public let font: UIFont
public let textColor: UIColor
// MARK: - Init
public init(viewModel: TextFieldViewModel, font: UIFont, textColor: UIColor) {
self.viewModel = viewModel
self.font = font
self.textColor = textColor
}
// MARK: - UIViewRepresentable
public func makeCoordinator() -> UITextFieldDelegate {
TextFieldCoordinator(viewModel: self.viewModel)
}
public func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.autocapitalizationType = .none
view.autocorrectionType = .no
view.clearButtonMode = .never
view.font = font
view.textColor = self.textColor
view.keyboardType = self.viewModel.keyboardType
view.placeholder = self.viewModel.placeholder
view.addTarget(context.coordinator,
action: #selector(TextFieldCoordinator.textViewDidChange),
for: .editingChanged)
view.delegate = context.coordinator
return view
}
public func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = self.viewModel.text
self.viewModel.isFirstResponder
? uiView.becomeFirstResponder()
: uiView.resignFirstResponder()
}
}
@@ -1,36 +0,0 @@
//
// TextFieldCoordinator.swift
// Malinka
//
// Created by NUT.Tech on 14.02.2023.
// Copyright © 2023 NUT.Tech. All rights reserved.
//
import SwiftUI
final class TextFieldCoordinator: NSObject, UITextFieldDelegate {
private let viewModel: TextFieldViewModel
init(viewModel: TextFieldViewModel) {
self.viewModel = viewModel
}
@objc
func textViewDidChange(_ textField: UITextField) {
self.viewModel.text = textField.text ?? ""
}
// MARK: - UITextFieldDelegate
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
guard self.viewModel.isFirstResponder == textField.isFirstResponder else { return false }
self.viewModel.isFirstResponder = true
return true
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
self.viewModel.isFirstResponder = false
return true
}
}
@@ -1,51 +0,0 @@
//
// TextFieldViewModel.swift
//
//
// Created by Juraldinio on 2/19/23.
//
import Foundation
import UIKit
import Combine
public final class TextFieldViewModel: ObservableObject {
@Published
public var text = ""
@Published
public var isFirstResponder = false
@Published
public var shouldClear: Bool = false
public let placeholder: String
public let keyboardType: UIKeyboardType
public var isCloseButtonVisible: Bool { !self.text.isEmpty }
private var canclellables = Set<AnyCancellable>()
// MARK: - Init
public init(placeholder: String = "", keyboardType: UIKeyboardType = .default) {
self.placeholder = placeholder
self.keyboardType = keyboardType
self.$text
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
self?.shouldClear = value == ""
}
.store(in: &self.canclellables)
}
// MARK: - Methods
public func clearButtonAction() {
self.text = ""
self.isFirstResponder = false
self.shouldClear = false
}
}
@@ -1,8 +0,0 @@
//
// File.swift
//
//
// Created by Juraldinio on 2/16/23.
//
import Foundation
+72
View File
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>JMalinkaWallet.ipa</key>
<array>
<dict>
<key>architectures</key>
<array>
<string>arm64</string>
</array>
<key>bitcode</key>
<false/>
<key>buildNumber</key>
<string>12</string>
<key>certificate</key>
<dict>
<key>SHA1</key>
<string>35C003923C51D78DF01DBCBFF8DAC6666C09412D</string>
<key>dateExpires</key>
<string>8/4/22</string>
<key>type</key>
<string>iOS Distribution</string>
</dict>
<key>entitlements</key>
<dict>
<key>application-identifier</key>
<string>GENPCTDS3G.com.juraldinio.wallet</string>
<key>aps-environment</key>
<string>production</string>
<key>beta-reports-active</key>
<true/>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:paycashonline.test-app.link</string>
<string>applinks:paycashonline-alternate.test-app.link</string>
<string>applinks:paycashonline-alternate.app.link</string>
<string>applinks:paycashonline.app.link</string>
</array>
<key>com.apple.developer.devicecheck.appattest-environment</key>
<string>production</string>
<key>com.apple.developer.team-identifier</key>
<string>GENPCTDS3G</string>
<key>get-task-allow</key>
<false/>
</dict>
<key>name</key>
<string>JMalinkaWallet.app</string>
<key>profile</key>
<dict>
<key>UUID</key>
<string>215599e9-99cb-4fbc-832f-12be30a2bcb0</string>
<key>dateExpires</key>
<string>8/4/22</string>
<key>name</key>
<string>MalinkaWallet</string>
</dict>
<key>symbols</key>
<true/>
<key>team</key>
<dict>
<key>id</key>
<string>GENPCTDS3G</string>
<key>name</key>
<string></string>
</dict>
<key>versionNumber</key>
<string>1.0.0</string>
</dict>
</array>
</dict>
</plist>
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>generateAppStoreInformation</key>
<false/>
<key>manageAppVersionAndBuildNumber</key>
<true/>
<key>method</key>
<string>app-store</string>
<key>provisioningProfiles</key>
<dict>
<key>com.juraldinio.wallet</key>
<string>MalinkaWallet</string>
</dict>
<key>signingCertificate</key>
<string>Apple Distribution</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>GENPCTDS3G</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
File diff suppressed because it is too large Load Diff
-26
View File
@@ -1,26 +0,0 @@
### Xcode ###
.build/
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.xcuserstate
timeline.xctimeline
.swiftpm/xcode
CryptoSwift.xcframework
/Framework
.DS_Store
Carthage/Build
.idea
.vscode
-5
View File
@@ -1,5 +0,0 @@
Marcin Krzyzanowski <marcin.krzyzanowski@gmail.com> <758033+krzyzanowskim@users.noreply.github.com>
Marcin Krzyzanowski <marcin.krzyzanowski@gmail.com> <krzyzanowskim@users.noreply.github.com>
Marcin Krzyzanowski <marcin.krzyzanowski@gmail.com> <marcin@krzyzanowskim.com>
Marcin Krzyzanowski <marcin.krzyzanowski@gmail.com> <marcin.krzyzanowski@gmail.com>
Luis Reisewitz <reisewitz@me.com> <zweigraf@users.noreply.github.com>
@@ -1,36 +0,0 @@
--exclude .build, Carthage, DerivedData, .git, Tests/LinuxMain.swift, Tests/CryptoSwiftTests/XCTestManifests.swift, Tests/TestsPerformance/XCTestManifests.swift
--swiftversion 5.0
--allman false
--commas always
--comments indent
--elseposition same-line
--empty void
--exponentcase lowercase
--exponentgrouping disabled
--fractiongrouping disabled
--header ignore
--octalgrouping 4,8
--decimalgrouping 3,6
--binarygrouping 4,8
--hexgrouping ignore
--hexliteralcase lowercase
--ifdef indent
--indent 2
--indentcase true
--importgrouping testable-bottom
--linebreaks lf
--operatorfunc spaced
--patternlet inline
--ranges no-space
--self insert
--semicolons inline
--stripunusedargs closure-only
--trimwhitespace always
--wraparguments preserve
--wrapcollections before-first
# rules
--rules indent, braces, isEmpty, redundantBreak, blankLinesAroundMark, blankLinesAtEndOfScope, blankLinesBetweenScopes, consecutiveBlankLines, consecutiveSpaces, duplicateImports, elseOnSameLine, leadingDelimiters, redundantBreak, redundantExtensionACL, redundantFileprivate, redundantGet, redundantInit, redundantLet, redundantNilInit, redundantObjc, redundantParens, redundantPattern, redundantRawValues, redundantReturn, redundantSelf, redundantVoidReturnType, semicolons, sortedImports, spaceAroundBraces, spaceAroundBrackets, spaceAroundComments, spaceAroundGenerics, spaceAroundOperators, spaceAroundParens, spaceInsideBraces, spaceInsideBrackets, specifiers, strongOutlets, strongifiedSelf, todos, void, wrapArguments, yodaConditions, trailingSpace
-13
View File
@@ -1,13 +0,0 @@
language: generic
matrix:
include:
# Test Ubuntu Linux 14.04
- os: linux
dist: trusty
sudo: required
- os: osx
osx_image: xcode11.4
install:
- eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
script:
- swift test -c release -Xswiftc -enable-testing -Xswiftc -DCI -Xswiftc -Xfrontend -Xswiftc -solver-memory-threshold -Xswiftc -Xfrontend -Xswiftc 999999999
-342
View File
@@ -1,342 +0,0 @@
1.5.1
- Resolve type name clash by renaming BigInt -> BigInteger
1.5.0
- RSA (@NathanFallet)
- Workaround for Xcode 13.3.1
1.4.3
- Fix PCBC mode.
1.4.2
- Update Xcode project to Xcode 13
- Add SHA3 support for HMAC
- Update HMAC.Variant API (deprecate current cases)
1.4.1
- Introduce ISO 10126 padding
- fix various compiler warnings
- Revert Xcode project deployment target
1.4.0
- Customize CFB segment size (cfb8, cfb128).
- Adapt Swift @inlineable for better code optimization
1.3.8
- Revert xcframework revolution. Back to build from sources. (I'm sorry)
1.3.7
- Re-release to workaround Swift Package Manager release
1.3.6
- Fix macOS binary
- Windows support
1.3.5
- Re-release binary framework due to codesign issue
1.3.4
- Distribute optimized binary (xcframework) via SPM for apple platforms
1.3.3
- Adds OCB Authenticated-Encryption Algorithm (RFC7253)
- build-framework.sh output CryptoSwift.xcframework
- Xcode 12.2 maintenance updates
- Removed CryptoSwift.playground (couldn't make it work since Xcode 12 update)
1.3.2
- Swift 5.3 update (Xcode 12)
- Bump target platform (iOS 9, macOS 10.12)
- Allow CMAC with any Cipher
- Remove CMAC key limit
1.3.1
- Fix tests
- Swift 5.2 update
- Address possible timing issue
1.3.0
- Adds ISO-78164 padding
- Performance improvements
- Swift 5.1 update
1.2.0
- Performance improvements
- Workaround Xcode test builds with Xcode 11
1.1.3
- Fix build crash: https://bugs.swift.org/browse/SR-11630
- Fixes Xcode project tests build
- SwiftFormat all the things
- Increase/fix SHA2 data length for big input by use Int64 for calculation
1.1.2
- Fix Swift 5.0 build (for real this time)
1.1.1
- Fix Swift 5.0 build
1.1.0
- Replace RandomBytesSequence with Swift.RandomNumberGenerator
- Fix CBC-MAC
- Update SPM support
- Update for Xcode 11 and Swift 5.1
- Xcode: BUILD_LIBRARY_FOR_DISTRIBUTION = YES
1.0.0
- Swift 5
- Let's
- Celebrate
- This
- Event
- With
- 1.0.0 release
- After
- 4 years
- Thank you
0.15.0
- Adds The scrypt Password-Based Key Derivation Function (https://tools.ietf.org/html/rfc7914)
- Minor improvements
0.14.0
- Fixed decryption of AES-GCM ciphertexts with custom tag length
0.13.1
- Adds AES-GCM tag length configuration.
- Fixes count check for initializing UInt64 from Data.
0.13.0
- Adds CBC-MAC authenticator.
- Adds AES-CCM operation mode.
0.12.0
- Swift 4.2 maintenance update.
0.11.0
- API: Cryptor.seek() is throwable
- Adds proper stream support for CTR encryption with Updaptable interface.
- Refactor internals for the stream cipher modes.
- Set minimum deployment target to 8.0 (again).
0.10.0
- API: BlockMode is no longer an enum. Please migrate to eg. CBC() etc...
- Adds AES-GCM support. #97 - Feature sponsored by GesundheitsCloud (http://www.gesundheitscloud.de/)
- Adds CRC32c support.
- Improves AES variant validation.
- Fixes empty password in PBKDF2.
0.9.0
- Swift 4.1 compatibility
- Added CMAC message authenticator https://tools.ietf.org/html/rfc4493
- Added AEADChaCha20Poly1305 (AEAD_CHACHA20_POLY1305) https://tools.ietf.org/html/rfc7539#section-2.8.1
0.8.3
- Fixes SHA3 padding.
- Fixes Carthage builds.
0.8.2
- Fixes SHA3 partial updates calculations.
- Makes ChaCha20 processing faster again.
0.8.1
- Adds Data(hex:) helper.
- Adds HKDF (HMAC-based Extract-and-Expand Key Derivation Function)
- Prevent ChaCha overflow error
0.8.0
- Adds SHA3 Keccak variants
- Adds String.bytes helper to convert String to array of bytes
- Improves AES performance
- Speeds up compilation times with Swift 4
- Fixes: Blowfish minimum key size is 5
- Removes Ciphers "iv" parameter (value moved to BlockMode)
- BlockMode uses associated value for IV value where apply e.g. .CBC(iv: ivbytes)
- Refactors internal hacks no longer needed with Swift 4
0.7.2
- Adds Padding enum (.pkcs5, .pkcs7, .noPadding, .zeroPadding)
- Removes Generics from the public API.
- Slightly improves SHA1, SHA2, SHA3 performance.
- Update SPM configuration for Swift 4
0.7.1
- Swift 4.0 compatibility release
0.7.0
- Swift 3.2 compatibility release
0.6.9
- Fixed padding issue where padding was not properly added in CTR mode.
- Fixed thrown error on decrypting empty string,
- Fixed CI build script.
- Added String.encryptToBase64()
0.6.8
- Speed up MD5()
- Faster Array(hex:)
- Improve AES performance
- Fix tvOS bitcode
- Fix Blowfish CFB, OFB, CTR block modes.
- Fix Blowfish for 32-bit arch.
- Fix ChaCha20 preconditions
0.6.7
- Release for Xcode 8.2
- Fix playground example
0.6.6
- Rework ChaCha20
- Fix Poly1305
0.6.5
- Significant performance improvement when processing lange amount of data.
- Degeneric functions and change Sequence -> Collection in generic constraints.
0.6.4
- More performance improvements
- Add convenient Digest.sha2(bytes:variant)
- New: Blowfish cipher
0.6.3
- Hotfix release
- Fixes bitPadding() that breaks Digests calculations, introduced in 0.6.2
0.6.2
- SHA performance improvements by using less Swift in Swift
- Fix public access to all digests classes
0.6.1
- Update tests.
- New: RandomBytesSequence urandom values on Linux.
- Throw appropriate error for AES with invalid input where padding is needed.
- Improve performance, especially to SHA-1, SHA-2, PBKDF and related.
- Set deployment targets for all platform. Fixes Carthage builds.
- New: SHA-3 implementation (request #291)
- SHA-1 conforms to Updatable protocol and may be calculated incrementally.
- SHA-2 conforms to Updatable protocol and may be calculated incrementally.
0.6.0
- Remove bridge() workaround for Linux (not needed)
- make MD5() public
- Update README
- Convenience HMAC initializer for String input
0.6.0-beta2
- SHA-2 fix #319
- HashProtocol -> Digest and refactor
- MD5 conforms to Updatable protocol and may be calculated incrementally
- Cipher protocol accepts Collection input now
0.6.0-beta1
- Swift 3 compatibility
- Multiplatform, Single-scheme Xcode Project
- Swift Package Manager fully supported (build and tests)
- Improved Linux support
- Travis configuration added
- Public interface tests added
- enum Authenticator -> protocol Authenticator
- CRC -> Checksum
- String.encrypt() returns hex string instead of Array<UInt8>
- removed String.decrypt()
- enum Hash -> struct Hash
- Convenience initializer of Array of bytes with Hex string. Array<UInt8>(hex: "0xb1b1b2b2")
- Fix reusability of ChaCha20 instance
- Replace optional initializers with throwable initializers
- Allow to set initial counter explicitly (AES block modes). RandomAccessCryptor.seek()
0.5.2
- Fix AES-CTR incremental updates. #287
- Fixed PBKDF2 tests. #295
- Fixed assertion check in PKCS7. #288
- Updatable protocol accept SequenceType in place of Array
0.5.1
- Fixed PBKDF2 not taking key length parameter into account
- Switch to Array<> in code
0.5
- Added PBKDF1 https://tools.ietf.org/html/rfc2898#section-5.1
- Added PBKDF2 https://tools.ietf.org/html/rfc2898#section-5.2
- UpdatableCryptor protocol allows incremental encryption stream of data
- CryptoSwift.playground
- Docs update
- Added reflection control to CRC-32 (Luís Silva)
- Fix AES.init() (Pascal Pfiffner)
0.4.1
- fix NoPadding()
0.4
- Padding setup is now part of cipher constructor
- Added PBKDF2 http://tools.ietf.org/html/rfc2898#section-5.2
- Add BlockCipher protocol
- Rename Cipher -> CipherProtocol
- Remove build-frameworks.sh script
- Keep sensitive data in memory with SecureBytes
- Allows direct use of HMAC and Poly1305
- README update
- Fix missing Foundation import on Linux
0.3.1
- replace deprecated Bit with new enum.
0.3
- Swift 2.2 support
- use generators for cipher block modes should reduce memory overload.
- add OFB block mode
- add PCBC block mode
- String.decryptBase64ToString to decrypt Base64 encoded strings
- broke up complicated expressions which were taking ages to compile
0.2.3
- enable bitcode setting for Debug on an Apple TV
- faster compilation times
- improve padding functions
0.2.2
- Fix ChaCha20 cipher
- Replace for(;;) with for-in
- Workaround for "NSString are not yet implicitly convertible to String" on Linux
0.2.1
- Fix linux build
- re-add umbrella header
0.2
- Rabbit cipher (RFC4503)
- Linux Swift support
- Swift Package Manager support
- tvOS support
- Add optional seed to CRC
- Add umbrella header (CryptoSwift.h)
- Fix AES in CTR mode
- Fix no padding support for CTR and CFB block modes
- Fix access to AES.Error and ChaCha20.Error
0.1.1
- Fix Cococapods package (missing Foundation integration)
0.1.0
- Major performance improvements.
- Transition from Optionals to throw error.
- Replace enum Cipher with protocol for ciphers.
- Added CRC16
- Fixed AES CFB decryption
- Drop internal "Foundation" dependency, nonetheless it is supported as usual.
0.0.16
- Critical fix for private "md5" selector issue (#135)
0.0.15
- Fix 32-bit CTR block mode
- Carthage support update
- Mark as App-Extension-Safe API
0.0.14
- hexString -> toHextString() #105
- CTR (Counter mode)
- Hex string is lowercase now
- Carthage support
- Tests update
- Swift 2.0 support - overall update
-1
View File
@@ -1 +0,0 @@
cryptoswift.io
@@ -1,46 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at marcin@krzyzanowskim.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

Some files were not shown because too many files have changed in this diff Show More