Register device
This commit is contained in:
parent
105d48558f
commit
b867522188
33 changed files with 1134 additions and 11 deletions
5
sdk/.gitignore
vendored
Normal file
5
sdk/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
28
sdk/Package.swift
Normal file
28
sdk/Package.swift
Normal file
|
@ -0,0 +1,28 @@
|
|||
// swift-tools-version:5.3
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Sdk",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "Sdk",
|
||||
targets: ["Sdk"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
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: "Sdk",
|
||||
dependencies: []),
|
||||
.testTarget(
|
||||
name: "SdkTests",
|
||||
dependencies: ["Sdk"]),
|
||||
]
|
||||
)
|
3
sdk/README.md
Normal file
3
sdk/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Sdk
|
||||
|
||||
A description of this package.
|
26
sdk/Sources/Sdk/APIError.swift
Normal file
26
sdk/Sources/Sdk/APIError.swift
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 15/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
public extension Sdk {
|
||||
enum APIError: Error {
|
||||
case error(reason: String)
|
||||
case jsonSerialization
|
||||
case noEndpointURL
|
||||
case noFirebaseToken
|
||||
case noCertificate
|
||||
case noPrivateKey
|
||||
case noSignatureValues
|
||||
case urlError
|
||||
|
||||
case wrongToken
|
||||
case wrongPin
|
||||
}
|
||||
}
|
||||
|
21
sdk/Sources/Sdk/Extensions/Date.swift
Normal file
21
sdk/Sources/Sdk/Extensions/Date.swift
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 15/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
extension Date {
|
||||
func formattedString(_ format: String) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = format
|
||||
return formatter.string(from: self)
|
||||
}
|
||||
|
||||
var millisecondsSince1970: Int64 {
|
||||
Int64((self.timeIntervalSince1970 * 1000.0).rounded())
|
||||
}
|
||||
}
|
70
sdk/Sources/Sdk/Extensions/URLRequest.swift
Normal file
70
sdk/Sources/Sdk/Extensions/URLRequest.swift
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 15/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
extension URLRequest {
|
||||
func signed(with certificate: X509) throws -> URLRequest {
|
||||
// Create request
|
||||
var request = self
|
||||
|
||||
// Signing stuff
|
||||
guard let urlString = request.url?.absoluteString else {
|
||||
throw Sdk.APIError.urlError
|
||||
}
|
||||
|
||||
// Get private key
|
||||
guard let privateKeyRawData = certificate.getPrivateKeyData(format: .DER),
|
||||
let privateKeyString = String(data: privateKeyRawData, encoding: .utf8)?
|
||||
.split(separator: "\n")
|
||||
.dropFirst()
|
||||
.dropLast()
|
||||
.joined()
|
||||
.data(using: .utf8) else {
|
||||
throw Sdk.APIError.noPrivateKey
|
||||
}
|
||||
|
||||
// Create SecKey
|
||||
let attributes = [
|
||||
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
||||
]
|
||||
guard let privateKeyData = Data(base64Encoded: privateKeyString),
|
||||
let secKey = SecKeyCreateWithData(privateKeyData as NSData, attributes as NSDictionary, nil) else {
|
||||
throw Sdk.APIError.noPrivateKey
|
||||
}
|
||||
|
||||
// Get fingerprint
|
||||
guard let signatureValues = Sdk.Signer.getSignatureValues(body: request.httpBody, url: urlString, privateKey: secKey, fingerprint: certificate.getCertificateFingerprint().lowercased()) else {
|
||||
throw Sdk.APIError.noSignatureValues
|
||||
}
|
||||
|
||||
let now = Date()
|
||||
var vDate: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss"
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
|
||||
return "\(dateFormatter.string(from: now)) GMT"
|
||||
}
|
||||
|
||||
// Headers
|
||||
request.setValue("iOS", forHTTPHeaderField: "vOS")
|
||||
request.setValue("1", forHTTPHeaderField: "vAPI")
|
||||
request.setValue(vDate, forHTTPHeaderField: "vDate")
|
||||
request.setValue(signatureValues.canonicalURL, forHTTPHeaderField: "vCanonicalUrl")
|
||||
request.setValue(signatureValues.signature, forHTTPHeaderField: "Signature")
|
||||
|
||||
if let digest = signatureValues.digest {
|
||||
request.setValue("SHA-256=\(digest)", forHTTPHeaderField: "Digest")
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
}
|
235
sdk/Sources/Sdk/Sdk.swift
Normal file
235
sdk/Sources/Sdk/Sdk.swift
Normal file
|
@ -0,0 +1,235 @@
|
|||
//
|
||||
// Sdk.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 14/02/2021.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import os
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
public class Sdk {
|
||||
static private let libraryVersion: String = "v0-INTERNAL"
|
||||
|
||||
private let loggerSubsystem: String = "xyz.shameful.VulcanKit"
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
public let certificate: X509
|
||||
|
||||
// MARK: - Init
|
||||
public init(certificate: X509) {
|
||||
self.certificate = certificate
|
||||
}
|
||||
|
||||
// MARK: - Public functions
|
||||
|
||||
/// Logs in with supplied login data.
|
||||
/// - Parameters:
|
||||
/// - token: Login token
|
||||
/// - symbol: Login symbol
|
||||
/// - pin: Login PIN
|
||||
/// - deviceName: Name of the device
|
||||
/// - completionHandler: Callback
|
||||
public func login(token: String, symbol: String, pin: String, deviceModel: String, completionHandler: @escaping (Error?) -> Void) {
|
||||
let logger: Logger = Logger(subsystem: self.loggerSubsystem, category: "Login")
|
||||
logger.debug("Logging in...")
|
||||
|
||||
let endpointPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "http://komponenty.vulcan.net.pl/UonetPlusMobile/RoutingRules.txt")!)
|
||||
.mapError { $0 as Error }
|
||||
|
||||
// Firebase request
|
||||
var firebaseRequest: URLRequest = URLRequest(url: URL(string: "https://android.googleapis.com/checkin")!)
|
||||
firebaseRequest.httpMethod = "POST"
|
||||
firebaseRequest.setValue("application/json", forHTTPHeaderField: "Content-type")
|
||||
firebaseRequest.setValue("gzip", forHTTPHeaderField: "Accept-Encoding")
|
||||
|
||||
let firebaseRequestBody: [String: Any] = [
|
||||
"locale": "pl_PL",
|
||||
"digest": "",
|
||||
"checkin": [
|
||||
"iosbuild": [
|
||||
"model": deviceModel,
|
||||
"os_version": Self.libraryVersion
|
||||
],
|
||||
"last_checkin_msec": 0,
|
||||
"user_number": 0,
|
||||
"type": 2
|
||||
],
|
||||
"time_zone": TimeZone.current.identifier,
|
||||
"user_serial_number": 0,
|
||||
"id": 0,
|
||||
"logging_id": 0,
|
||||
"version": 2,
|
||||
"security_token": 0,
|
||||
"fragment": 0
|
||||
]
|
||||
firebaseRequest.httpBody = try? JSONSerialization.data(withJSONObject: firebaseRequestBody)
|
||||
|
||||
let firebasePublisher = URLSession.shared.dataTaskPublisher(for: firebaseRequest)
|
||||
.receive(on: DispatchQueue.global(qos: .background))
|
||||
.tryCompactMap { value -> AnyPublisher<Data, Error> in
|
||||
guard let dictionary: [String: Any] = try? JSONSerialization.jsonObject(with: value.data) as? [String: Any] else {
|
||||
throw APIError.jsonSerialization
|
||||
}
|
||||
|
||||
var request: URLRequest = URLRequest(url: URL(string: "https://fcmtoken.googleapis.com/register")!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("AidLogin \(dictionary["android_id"] as? Int ?? 0):\(dictionary["security_token"] as? Int ?? 0)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("gzip", forHTTPHeaderField: "Accept-Encoding")
|
||||
|
||||
let body: String = "device=\(dictionary["android_id"] as? Int ?? 0)&app=pl.edu.vulcan.hebe&sender=987828170337&X-subtype=987828170337&appid=dLIDwhjvE58&gmp_app_id=1:987828170337:ios:6b65a4ad435fba7f"
|
||||
request.httpBody = body.data(using: .utf8)
|
||||
|
||||
return URLSession.shared.dataTaskPublisher(for: request)
|
||||
.receive(on: DispatchQueue.global(qos: .background))
|
||||
.mapError { $0 as Error }
|
||||
.map { $0.data }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.flatMap { $0 }
|
||||
|
||||
Publishers.Zip(endpointPublisher, firebasePublisher)
|
||||
.tryMap { (endpoints, firebaseToken) -> (String, String) in
|
||||
// Find endpointURL
|
||||
let lines = String(data: endpoints.data, encoding: .utf8)?.split { $0.isNewline }
|
||||
|
||||
var endpointURL: String?
|
||||
lines?.forEach { line in
|
||||
let items = line.split(separator: ",")
|
||||
if (token.starts(with: items[0])) {
|
||||
endpointURL = String(items[1])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard let finalEndpointURL: String = endpointURL else {
|
||||
throw APIError.noEndpointURL
|
||||
}
|
||||
|
||||
// Get Firebase token
|
||||
guard let token: String = String(data: firebaseToken, encoding: .utf8)?.components(separatedBy: "token=").last else {
|
||||
logger.error("Token empty! Response: \"\(firebaseToken.base64EncodedString(), privacy: .private)\"")
|
||||
throw APIError.noFirebaseToken
|
||||
}
|
||||
|
||||
return (finalEndpointURL, token)
|
||||
}
|
||||
.tryMap { endpointURL, firebaseToken in
|
||||
try self.registerDevice(endpointURL: endpointURL, firebaseToken: firebaseToken, token: token, symbol: symbol, pin: pin, deviceModel: deviceModel)
|
||||
.mapError { $0 as Error }
|
||||
.map { $0.data }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.flatMap { $0 }
|
||||
.sink(receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .finished:
|
||||
break
|
||||
case .failure(let error):
|
||||
completionHandler(error)
|
||||
}
|
||||
}, receiveValue: { data in
|
||||
if let response = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let parsedError = self.parseResponse(response) {
|
||||
completionHandler(parsedError)
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
/// Registers the device
|
||||
/// - Parameters:
|
||||
/// - endpointURL: API endpoint URL
|
||||
/// - firebaseToken: FCM token
|
||||
/// - token: Vulcan token
|
||||
/// - symbol: Vulcan symbol
|
||||
/// - pin: Vulcan PIN
|
||||
/// - deviceModel: Device model
|
||||
/// - Throws: Error
|
||||
/// - Returns: URLSession.DataTaskPublisher
|
||||
private func registerDevice(endpointURL: String, firebaseToken: String, token: String, symbol: String, pin: String, deviceModel: String) throws -> URLSession.DataTaskPublisher {
|
||||
guard let keyFingerprint = certificate.getPrivateKeyFingerprint(format: .PEM)?.replacingOccurrences(of: ":", with: "").lowercased(),
|
||||
let keyData = certificate.getPublicKeyData(),
|
||||
let keyBase64 = String(data: keyData, encoding: .utf8)?
|
||||
.split(separator: "\n") // Split by newline
|
||||
.dropFirst() // Drop prefix
|
||||
.dropLast() // Drop suffix
|
||||
.joined() // Join
|
||||
else {
|
||||
throw APIError.noCertificate
|
||||
}
|
||||
|
||||
// Request
|
||||
let url = "\(endpointURL)/\(symbol)/api/mobile/register/new"
|
||||
var request = URLRequest(url: URL(string: url)!)
|
||||
request.httpMethod = "POST"
|
||||
|
||||
let now: Date = Date()
|
||||
var timestampFormatted: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss"
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
|
||||
return dateFormatter.string(from: now)
|
||||
}
|
||||
|
||||
// Body
|
||||
let body: [String: Encodable?] = [
|
||||
"AppName": "DzienniczekPlus 2.0",
|
||||
"AppVersion": Self.libraryVersion,
|
||||
"CertificateId": nil,
|
||||
"Envelope": [
|
||||
"OS": "iOS",
|
||||
"PIN": pin,
|
||||
"Certificate": keyBase64,
|
||||
"CertificateType": "RSA_PEM",
|
||||
"DeviceModel": deviceModel,
|
||||
"SecurityToken": token,
|
||||
"SelfIdentifier": UUID().uuidString.lowercased(),
|
||||
"CertificateThumbprint": keyFingerprint
|
||||
],
|
||||
"FirebaseToken": firebaseToken,
|
||||
"API": 1,
|
||||
"RequestId": UUID().uuidString.lowercased(),
|
||||
"Timestamp": now.millisecondsSince1970,
|
||||
"TimestampFormatted": "\(timestampFormatted) GMT"
|
||||
]
|
||||
|
||||
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
|
||||
|
||||
request.allHTTPHeaderFields = [
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "gzip",
|
||||
"vDeviceModel": deviceModel
|
||||
]
|
||||
|
||||
let signedRequest = try request.signed(with: certificate)
|
||||
return URLSession.shared.dataTaskPublisher(for: signedRequest)
|
||||
}
|
||||
|
||||
// MARK: - Helper functions
|
||||
|
||||
/// Parses the response
|
||||
/// - Parameter response: Request response
|
||||
/// - Returns: VulcanKit.APIError?
|
||||
private func parseResponse(_ response: [String: Any]) -> APIError? {
|
||||
guard let status = response["Status"] as? [String: Any],
|
||||
let statusCode = status["Code"] as? Int else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case 0: return nil
|
||||
case 200: return APIError.wrongToken
|
||||
case 203: return APIError.wrongPin
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
192
sdk/Sources/Sdk/X509.swift
Normal file
192
sdk/Sources/Sdk/X509.swift
Normal file
|
@ -0,0 +1,192 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 14/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import OpenSSL
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
public class X509: ObservableObject {
|
||||
enum X509Error: Error {
|
||||
case errorGeneratingPKEY
|
||||
}
|
||||
|
||||
public enum KeyFormat {
|
||||
case PEM
|
||||
case DER
|
||||
}
|
||||
|
||||
let certificate: OpaquePointer
|
||||
let pkey: OpaquePointer
|
||||
|
||||
public init(serialNumber: Int, certificateEntries: [String: String]) throws {
|
||||
let x509: OpaquePointer = X509_new()
|
||||
|
||||
// serial number
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(x509), serialNumber)
|
||||
|
||||
// version
|
||||
X509_set_version(x509, 0x2) // v3
|
||||
|
||||
// validity date
|
||||
X509_gmtime_adj(X509_getm_notBefore(x509), 0)
|
||||
X509_gmtime_adj(X509_getm_notAfter(x509), 60 * 60 * 24 * 365 * 10) // 60 seconds * 60 minutes * 24 hours * 365 days * 10 years
|
||||
|
||||
// key
|
||||
guard let pkey = EVP_PKEY_new() else {
|
||||
throw X509Error.errorGeneratingPKEY
|
||||
}
|
||||
|
||||
let exponent = BN_new()
|
||||
BN_set_word(exponent, 0x10001)
|
||||
|
||||
let rsa = RSA_new()
|
||||
RSA_generate_key_ex(rsa, 2048, exponent, nil)
|
||||
EVP_PKEY_set1_RSA(pkey, rsa)
|
||||
|
||||
X509_set_pubkey(x509, pkey)
|
||||
self.pkey = pkey
|
||||
|
||||
// issuer
|
||||
let subjectName = X509_get_subject_name(x509)
|
||||
for (key, value) in certificateEntries {
|
||||
X509_NAME_add_entry_by_txt(subjectName, key, MBSTRING_ASC, value, -1, -1, 0)
|
||||
}
|
||||
|
||||
X509_set_issuer_name(x509, subjectName)
|
||||
|
||||
// sign the certificate
|
||||
X509_sign(x509, pkey, EVP_sha256())
|
||||
|
||||
self.certificate = x509
|
||||
}
|
||||
|
||||
/// Gets the private key used to sign the certificate data.
|
||||
/// - Parameter format: Format of the returned key
|
||||
/// - Returns: Private key data
|
||||
public func getPrivateKeyData(format: KeyFormat) -> Data? {
|
||||
let bio = BIO_new(BIO_s_mem())
|
||||
|
||||
switch format {
|
||||
case .PEM: PEM_write_bio_PrivateKey(bio, self.pkey, nil, nil, 0, nil, nil)
|
||||
case .DER: PEM_write_bio_PrivateKey_traditional(bio, self.pkey, nil, nil, 0, nil, nil)
|
||||
}
|
||||
|
||||
var pointer: UnsafeMutableRawPointer?
|
||||
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
|
||||
|
||||
guard let nonEmptyPointer = pointer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let data = Data(bytes: nonEmptyPointer, count: len)
|
||||
BIO_vfree(bio)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/// Gets the public key used to sign the certificate data.
|
||||
/// - Returns: Public key data
|
||||
public func getPublicKeyData() -> Data? {
|
||||
let bio = BIO_new(BIO_s_mem())
|
||||
PEM_write_bio_PUBKEY(bio, self.pkey)
|
||||
|
||||
var pointer: UnsafeMutableRawPointer?
|
||||
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
|
||||
|
||||
guard let nonEmptyPointer = pointer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let data = Data(bytes: nonEmptyPointer, count: len)
|
||||
BIO_vfree(bio)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/// Gets the generated certificate data.
|
||||
/// - Returns: Certificate data
|
||||
public func getCertificateData() -> Data? {
|
||||
let bio = BIO_new(BIO_s_mem())
|
||||
PEM_write_bio_X509(bio, self.certificate)
|
||||
|
||||
var pointer: UnsafeMutableRawPointer?
|
||||
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
|
||||
|
||||
guard let nonEmptyPointer = pointer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let data = Data(bytes: nonEmptyPointer, count: len)
|
||||
BIO_vfree(bio)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/// Get certificate thumbrint.
|
||||
/// - Returns: Certificate fingerprint
|
||||
public func getCertificateFingerprint() -> String {
|
||||
let md: UnsafeMutablePointer<UInt8> = .allocate(capacity: Int(EVP_MAX_MD_SIZE))
|
||||
var n: UInt32 = 0
|
||||
|
||||
X509_digest(self.certificate, EVP_sha1(), md, &n)
|
||||
return UnsafeMutableBufferPointer(start: md, count: Int(EVP_MAX_MD_SIZE))
|
||||
.prefix(Int(n))
|
||||
.makeIterator()
|
||||
.map {
|
||||
let string = String($0, radix: 16)
|
||||
return ($0 < 16 ? "0" + string : string)
|
||||
}
|
||||
.joined(separator: ":")
|
||||
.uppercased()
|
||||
}
|
||||
|
||||
/// Get public key fingerprint
|
||||
/// - Returns: Public key fingerprint
|
||||
public func getPublicKeyFingerprint() -> String? {
|
||||
guard let keyData = self.getPublicKeyData(),
|
||||
let rawKeyB64 = String(data: keyData, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let keyB64 = rawKeyB64
|
||||
.split(separator: "\n") // Split by newline
|
||||
.dropFirst() // Drop prefix
|
||||
.dropLast() // Drop suffix
|
||||
.joined() // Combine
|
||||
|
||||
guard let data = Data(base64Encoded: keyB64) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let hash = Insecure.MD5.hash(data: data)
|
||||
return hash.map { String(format: "%02hhx", $0) }.joined()
|
||||
}
|
||||
|
||||
/// Get private key fingerprint
|
||||
/// - Parameter format: Format of the returned key
|
||||
/// - Returns: Private key fingerprint
|
||||
public func getPrivateKeyFingerprint(format: KeyFormat) -> String? {
|
||||
guard let keyData = self.getPrivateKeyData(format: format),
|
||||
let rawKeyB64 = String(data: keyData, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let keyB64 = rawKeyB64
|
||||
.split(separator: "\n") // Split by newline
|
||||
.dropFirst() // Drop prefix
|
||||
.dropLast() // Drop suffix
|
||||
.joined() // Combine
|
||||
|
||||
guard let data = Data(base64Encoded: keyB64) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let hash = Insecure.MD5.hash(data: data)
|
||||
return hash.map { String(format: "%02hhx", $0) }.joined()
|
||||
}
|
||||
}
|
97
sdk/Sources/Sdk/signer.swift
Normal file
97
sdk/Sources/Sdk/signer.swift
Normal file
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 14/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
public extension Sdk {
|
||||
struct Signer {
|
||||
static public func getSignatureValues(body: Data?, url: String, date: Date = Date(), privateKey: SecKey, fingerprint: String) -> (digest: String?, canonicalURL: String, signature: String)? {
|
||||
// Canonical URL
|
||||
guard let canonicalURL = getCanonicalURL(url) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Digest
|
||||
let digest: String?
|
||||
if let body = body {
|
||||
digest = Data(SHA256.hash(data: body)).base64EncodedString()
|
||||
} else {
|
||||
digest = nil
|
||||
}
|
||||
|
||||
// Headers & values
|
||||
let headersList = getHeadersList(digest: digest, canonicalURL: canonicalURL, date: date)
|
||||
|
||||
// Signature value
|
||||
guard let data = headersList.values.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let signatureData = SecKeyCreateSignature(privateKey, .rsaSignatureMessagePKCS1v15SHA256, data as CFData, nil) as Data?
|
||||
guard let signatureValue = signatureData?.base64EncodedString() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (
|
||||
digest,
|
||||
canonicalURL,
|
||||
"keyId=\"\(fingerprint.replacingOccurrences(of: ":", with: ""))\",headers=\"\(headersList.headers)\",algorithm=\"sha256withrsa\",signature=Base64(SHA256withRSA(\(signatureValue)))"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
/// Finds and encodes the first canonical URL match in the supplied URL.
|
||||
/// - Parameter url: URL to find matches in
|
||||
/// - Returns: Canonical URL
|
||||
static internal func getCanonicalURL(_ url: String) -> String? {
|
||||
guard let regex = try? NSRegularExpression(pattern: "(api/mobile/.+)") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let results = regex.matches(in: url, range: NSRange(url.startIndex..., in: url))
|
||||
return results.compactMap {
|
||||
guard let range = Range($0.range, in: url) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return String(url[range]).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)?.lowercased()
|
||||
}
|
||||
.first
|
||||
}
|
||||
|
||||
/// Creates a tuple with formatted headers and values needed to sign the request.
|
||||
/// - Parameters:
|
||||
/// - body: Body of the request
|
||||
/// - digest: Digest of the request
|
||||
/// - canonicalURL: Canonical URL of the request
|
||||
/// - date: Date of the request
|
||||
/// - Returns: Formatted headers and values
|
||||
static internal func getHeadersList(digest: String?, canonicalURL: String, date: Date) -> (headers: String, values: String) {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss"
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
|
||||
let dateString = "\(dateFormatter.string(from: date)) GMT"
|
||||
|
||||
let signData: [(key: String, value: String)] = [
|
||||
("vCanonicalUrl", canonicalURL),
|
||||
digest == nil ? nil : ("Digest", digest ?? ""),
|
||||
("vDate", "\(dateString)")
|
||||
]
|
||||
.compactMap { $0 }
|
||||
|
||||
let headers = signData.map(\.key).joined(separator: " ")
|
||||
let values = signData.map(\.value).joined()
|
||||
|
||||
return (headers, values)
|
||||
}
|
||||
}
|
||||
}
|
7
sdk/Tests/LinuxMain.swift
Normal file
7
sdk/Tests/LinuxMain.swift
Normal file
|
@ -0,0 +1,7 @@
|
|||
import XCTest
|
||||
|
||||
import SdkTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += SdkTests.allTests()
|
||||
XCTMain(tests)
|
15
sdk/Tests/SdkTests/SdkTests.swift
Normal file
15
sdk/Tests/SdkTests/SdkTests.swift
Normal file
|
@ -0,0 +1,15 @@
|
|||
import XCTest
|
||||
@testable import Sdk
|
||||
|
||||
final class SdkTests: XCTestCase {
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(Sdk().text, "Hello, World!")
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testExample", testExample),
|
||||
]
|
||||
}
|
9
sdk/Tests/SdkTests/XCTestManifests.swift
Normal file
9
sdk/Tests/SdkTests/XCTestManifests.swift
Normal file
|
@ -0,0 +1,9 @@
|
|||
import XCTest
|
||||
|
||||
#if !canImport(ObjectiveC)
|
||||
public func allTests() -> [XCTestCaseEntry] {
|
||||
return [
|
||||
testCase(SdkTests.allTests),
|
||||
]
|
||||
}
|
||||
#endif
|
|
@ -3,10 +3,14 @@
|
|||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objectVersion = 52;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5C478F3525DC742100ABEFB7 /* VulcanStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C478F3425DC742100ABEFB7 /* VulcanStore.swift */; };
|
||||
5C9B6F4925D6C08D00C3F5F5 /* Sdk in Frameworks */ = {isa = PBXBuildFile; productRef = 5C9B6F4825D6C08D00C3F5F5 /* Sdk */; };
|
||||
5CCAE31625DA4CDD00D87580 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 5CCAE31525DA4CDD00D87580 /* OpenSSL */; };
|
||||
5CEA516B25D540B900DB45BD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CEA516D25D540B900DB45BD /* Localizable.strings */; };
|
||||
F4C6D9082544E17400F8903A /* wulkanowyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C6D9072544E17400F8903A /* wulkanowyApp.swift */; };
|
||||
F4C6D90A2544E17400F8903A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C6D9092544E17400F8903A /* ContentView.swift */; };
|
||||
F4C6D90C2544E17500F8903A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4C6D90B2544E17500F8903A /* Assets.xcassets */; };
|
||||
|
@ -32,7 +36,35 @@
|
|||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
5C1F6D5F25D6891300AFDDD6 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C9B6EEC25D6B25200C3F5F5 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5C478F3425DC742100ABEFB7 /* VulcanStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VulcanStore.swift; sourceTree = "<group>"; };
|
||||
5C9B6E4925D6ADFB00C3F5F5 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
5C9B6F4525D6C06D00C3F5F5 /* Sdk */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Sdk; sourceTree = "<group>"; };
|
||||
5CEA516C25D540B900DB45BD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CF81BD725D9D44400B12C4C /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
F4C6D9042544E17400F8903A /* wulkanowy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = wulkanowy.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F4C6D9072544E17400F8903A /* wulkanowyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = wulkanowyApp.swift; sourceTree = "<group>"; };
|
||||
F4C6D9092544E17400F8903A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
|
@ -52,6 +84,8 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C9B6F4925D6C08D00C3F5F5 /* Sdk in Frameworks */,
|
||||
5CCAE31625DA4CDD00D87580 /* OpenSSL in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -72,12 +106,22 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
5C9B6E4825D6ADFB00C3F5F5 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C9B6E4925D6ADFB00C3F5F5 /* NetworkExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F4C6D8FB2544E17300F8903A = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C9B6F4525D6C06D00C3F5F5 /* Sdk */,
|
||||
F4C6D9062544E17400F8903A /* wulkanowy */,
|
||||
F4C6D9182544E17500F8903A /* wulkanowyTests */,
|
||||
F4C6D9232544E17500F8903A /* wulkanowyUITests */,
|
||||
5C9B6E4825D6ADFB00C3F5F5 /* Frameworks */,
|
||||
F4C6D9052544E17400F8903A /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
|
@ -100,6 +144,8 @@
|
|||
F4C6D90B2544E17500F8903A /* Assets.xcassets */,
|
||||
F4C6D9102544E17500F8903A /* Info.plist */,
|
||||
F4C6D90D2544E17500F8903A /* Preview Content */,
|
||||
5CEA516D25D540B900DB45BD /* Localizable.strings */,
|
||||
5C478F3425DC742100ABEFB7 /* VulcanStore.swift */,
|
||||
);
|
||||
path = wulkanowy;
|
||||
sourceTree = "<group>";
|
||||
|
@ -140,12 +186,18 @@
|
|||
F4C6D9002544E17300F8903A /* Sources */,
|
||||
F4C6D9012544E17300F8903A /* Frameworks */,
|
||||
F4C6D9022544E17300F8903A /* Resources */,
|
||||
5C1F6D5F25D6891300AFDDD6 /* Embed App Extensions */,
|
||||
5C9B6EEC25D6B25200C3F5F5 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = wulkanowy;
|
||||
packageProductDependencies = (
|
||||
5C9B6F4825D6C08D00C3F5F5 /* Sdk */,
|
||||
5CCAE31525DA4CDD00D87580 /* OpenSSL */,
|
||||
);
|
||||
productName = wulkanowy;
|
||||
productReference = F4C6D9042544E17400F8903A /* wulkanowy.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
|
@ -192,7 +244,7 @@
|
|||
F4C6D8FC2544E17300F8903A /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1210;
|
||||
LastSwiftUpdateCheck = 1240;
|
||||
LastUpgradeCheck = 1210;
|
||||
TargetAttributes = {
|
||||
F4C6D9032544E17300F8903A = {
|
||||
|
@ -215,8 +267,12 @@
|
|||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
"pl-PL",
|
||||
);
|
||||
mainGroup = F4C6D8FB2544E17300F8903A;
|
||||
packageReferences = (
|
||||
5CCAE31025DA4CCA00D87580 /* XCRemoteSwiftPackageReference "OpenSSL" */,
|
||||
);
|
||||
productRefGroup = F4C6D9052544E17400F8903A /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
|
@ -234,6 +290,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4C6D90F2544E17500F8903A /* Preview Assets.xcassets in Resources */,
|
||||
5CEA516B25D540B900DB45BD /* Localizable.strings in Resources */,
|
||||
F4C6D90C2544E17500F8903A /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -259,6 +316,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C478F3525DC742100ABEFB7 /* VulcanStore.swift in Sources */,
|
||||
F4C6D90A2544E17400F8903A /* ContentView.swift in Sources */,
|
||||
F4C6D9082544E17400F8903A /* wulkanowyApp.swift in Sources */,
|
||||
);
|
||||
|
@ -295,11 +353,24 @@
|
|||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
5CEA516D25D540B900DB45BD /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
5CEA516C25D540B900DB45BD /* en */,
|
||||
5CF81BD725D9D44400B12C4C /* pl-PL */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
F4C6D9272544E17500F8903A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
|
@ -347,7 +418,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
@ -361,6 +432,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
|
@ -402,7 +474,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -419,14 +491,14 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"wulkanowy/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = wulkanowy/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.github.wulkanowy;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
@ -440,14 +512,14 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"wulkanowy/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = wulkanowy/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.github.wulkanowy;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
@ -574,6 +646,29 @@
|
|||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
5CCAE31025DA4CCA00D87580 /* XCRemoteSwiftPackageReference "OpenSSL" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/krzyzanowskim/OpenSSL";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.1.180;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
5C9B6F4825D6C08D00C3F5F5 /* Sdk */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Sdk;
|
||||
};
|
||||
5CCAE31525DA4CDD00D87580 /* OpenSSL */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5CCAE31025DA4CCA00D87580 /* XCRemoteSwiftPackageReference "OpenSSL" */;
|
||||
productName = OpenSSL;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = F4C6D8FC2544E17300F8903A /* Project object */;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?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>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "OpenSSL",
|
||||
"repositoryURL": "https://github.com/krzyzanowskim/OpenSSL",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "389296819a8d025ac10ddc9f22135a5518991fdc",
|
||||
"version": "1.1.180"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
10
wulkanowy.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
wulkanowy.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:wulkanowy.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,8 @@
|
|||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
BIN
wulkanowy/.DS_Store
vendored
Normal file
BIN
wulkanowy/.DS_Store
vendored
Normal file
Binary file not shown.
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x09",
|
||||
"green" : "0x02",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "logo.jpg",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
|
|
BIN
wulkanowy/Assets.xcassets/AppIcon.appiconset/logo.jpg
Normal file
BIN
wulkanowy/Assets.xcassets/AppIcon.appiconset/logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.184",
|
||||
"green" : "0.184",
|
||||
"red" : "0.827"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.184",
|
||||
"green" : "0.184",
|
||||
"red" : "0.827"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
6
wulkanowy/Assets.xcassets/Colours/Contents.json
Normal file
6
wulkanowy/Assets.xcassets/Colours/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
23
wulkanowy/Assets.xcassets/wulkanowy.imageset/Contents.json
vendored
Normal file
23
wulkanowy/Assets.xcassets/wulkanowy.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "wulkanowy-1.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "wulkanowy.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "wulkanowy-2.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
9
wulkanowy/Assets.xcassets/wulkanowy.imageset/wulkanowy-1.svg
vendored
Normal file
9
wulkanowy/Assets.xcassets/wulkanowy.imageset/wulkanowy-1.svg
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Shape</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="wulkanowy" fill="#000000" fill-rule="nonzero">
|
||||
<path d="M295.17362,965.05417 C296.24282,968.52944 295.70822,972.27203 293.83692,975.34631 L268.03972,1017.1831 C265.50012,1021.3267 260.82192,1024 255.74272,1024 L14.345318,1024 C3.1176178,1024 -3.6991822,1012.2376 2.3157178,1003.4158 L157.76692,774.44928 C158.70252,773.11265 159.23732,771.64234 159.63822,770.17205 L230.88102,465.95272 C231.68312,462.34379 234.08912,459.26952 237.56432,457.39823 L334.06972,404.46727 C337.54502,402.59597 339.95092,399.5217 340.75292,395.77911 L353.71832,339.23923 C356.39162,327.47681 373.23342,324.93718 379.91642,335.22932 L384.59472,342.71451 C386.59962,345.92244 387.13432,349.93236 385.79782,353.54129 L297.84672,607.77024 C297.17842,609.77521 296.91122,611.91384 297.31212,614.05247 L319.23302,735.68673 C319.63402,738.09268 319.36642,740.63229 318.29732,742.90458 L266.03482,860.26158 C264.83182,863.06854 264.56442,866.14281 265.50012,868.94975 L295.17362,965.05417 Z M1009.7413,1024 L843.46322,1024 C838.65152,1024 834.24042,1021.5941 831.56732,1017.8515 L719.69042,860.52891 C719.02212,859.45961 718.35382,858.3903 717.95292,857.18731 L662.48222,695.18653 C661.41302,691.9786 658.87342,689.17164 655.66532,687.56767 L519.86272,618.99802 C516.12012,617.12675 513.44682,613.65147 512.64482,609.77521 L492.59532,510.32918 C492.32792,508.72522 491.65962,507.12125 490.59032,505.65094 L444.47622,437.88328 C441.93662,434.14069 441.53572,429.59611 443.40692,425.58618 L471.47632,365.57105 C473.61502,361.02648 478.29332,357.95219 483.63972,357.68486 L535.76872,354.61059 C538.84292,354.47689 541.64992,353.40763 543.92232,351.53632 L582.28382,321.72925 C589.50162,316.11537 600.46222,318.52131 604.33842,326.40749 L736.53212,595.3395 C737.06672,596.54247 737.46782,597.74545 737.73502,598.94844 L754.04222,707.08262 C754.44312,709.62225 755.51232,711.89452 757.25012,713.76582 L1020.5682,1001.9454 C1028.3207,1010.4999 1021.7712,1024 1009.7413,1024 L1009.7413,1024 Z M363.20822,182.58501 C363.20822,151.97594 382.58972,125.64413 410.39192,113.34703 C408.38692,110.00543 407.18402,106.39651 407.18402,102.52025 C407.18402,87.683545 424.29302,75.653785 445.27822,75.653785 L446.74862,75.653785 C455.43662,61.218065 472.01102,51.460605 490.99122,51.460605 C492.32792,51.460605 493.66452,51.460605 495.00122,51.594305 C496.73892,51.727995 498.47652,50.925975 499.41212,49.589335 C513.44682,28.203095 549.00152,12.965395 590.57112,12.965395 C605.94232,12.965395 620.51172,15.104025 633.47712,18.712955 C636.55152,13.633715 643.36822,10.158455 651.25442,10.158455 C660.21002,10.158455 667.82882,14.703035 670.10102,20.985235 C681.06162,8.287145 699.64082,-4.99999999e-06 720.75972,-4.99999999e-06 C754.44312,-4.99999999e-06 781.71052,21.252565 781.71052,47.450715 C781.71052,50.658645 781.30952,53.732915 780.50752,56.807185 C779.97292,58.945825 781.17582,61.084435 783.44822,61.886425 C804.96812,69.772605 819.53752,84.742975 819.53752,101.85196 C819.53752,121.36691 800.69092,138.07492 774.09172,144.62445 C771.95302,145.15911 770.61642,147.0304 770.61642,149.03537 L770.61642,149.43635 C770.61642,164.54039 755.64602,176.97115 736.39842,178.30779 C736.53172,178.97612 736.53172,179.64442 736.53172,180.44641 C736.53172,209.4515 681.32862,232.84271 613.29352,232.84271 C598.59062,232.84271 584.55582,231.77339 571.59042,229.76844 L571.59042,230.43676 C571.59042,242.46651 556.08532,252.22399 537.10502,252.22399 C536.03582,252.22399 535.10012,252.22399 534.16452,252.0903 C535.50122,255.0309 536.16952,258.10517 536.16952,261.31311 C536.16952,280.02607 512.51092,295.1301 483.23842,295.1301 C480.03052,295.1301 476.95632,294.9964 473.88212,294.59544 C471.47602,294.32813 469.20382,295.79843 468.66902,298.07073 C466.12942,307.4272 457.97602,314.24406 448.21862,314.24406 C436.45612,314.24406 427.09962,304.21926 427.09962,291.92217 C427.09962,286.17462 429.10462,280.96172 432.44622,277.08546 C433.64922,275.74883 434.05012,273.87753 433.24812,272.27355 C431.37682,268.79829 430.57482,265.18936 430.57482,261.31311 C430.57482,259.17449 428.83722,257.43685 426.69852,256.90221 C390.47572,248.88236 363.20822,218.67429 363.20822,182.58501 L363.20822,182.58501 Z M670.10102,908.64795 C670.63582,910.25193 670.76932,911.85591 670.76932,913.59353 L663.01682,1011.5693 C662.48222,1018.5198 656.33362,1024 648.84852,1024 L398.62952,1024 C393.28292,1024 388.33742,1020.9257 385.93132,1016.5148 L344.62922,939.79168 C344.36192,939.39068 344.22822,938.98969 344.09452,938.5887 L305.19832,844.35557 C303.72812,841.01397 303.99532,837.13772 305.59942,833.92976 L370.15902,707.61727 C371.76302,704.543 372.03022,700.93407 370.82732,697.72613 L339.28262,610.30987 C338.21332,607.23559 338.34702,603.76032 339.68372,600.81972 L392.34732,488.6756 C397.69372,477.44782 415.20382,478.38348 418.94642,490.14591 L435.38712,541.20556 L485.51112,675.40424 C486.84782,679.14684 490.05572,682.08744 494.06562,683.42409 L600.99682,719.91436 C605.14032,721.38468 608.34842,724.45894 609.68492,728.3352 L670.10102,908.64795 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
9
wulkanowy/Assets.xcassets/wulkanowy.imageset/wulkanowy-2.svg
vendored
Normal file
9
wulkanowy/Assets.xcassets/wulkanowy.imageset/wulkanowy-2.svg
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Shape</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="wulkanowy" fill="#000000" fill-rule="nonzero">
|
||||
<path d="M295.17362,965.05417 C296.24282,968.52944 295.70822,972.27203 293.83692,975.34631 L268.03972,1017.1831 C265.50012,1021.3267 260.82192,1024 255.74272,1024 L14.345318,1024 C3.1176178,1024 -3.6991822,1012.2376 2.3157178,1003.4158 L157.76692,774.44928 C158.70252,773.11265 159.23732,771.64234 159.63822,770.17205 L230.88102,465.95272 C231.68312,462.34379 234.08912,459.26952 237.56432,457.39823 L334.06972,404.46727 C337.54502,402.59597 339.95092,399.5217 340.75292,395.77911 L353.71832,339.23923 C356.39162,327.47681 373.23342,324.93718 379.91642,335.22932 L384.59472,342.71451 C386.59962,345.92244 387.13432,349.93236 385.79782,353.54129 L297.84672,607.77024 C297.17842,609.77521 296.91122,611.91384 297.31212,614.05247 L319.23302,735.68673 C319.63402,738.09268 319.36642,740.63229 318.29732,742.90458 L266.03482,860.26158 C264.83182,863.06854 264.56442,866.14281 265.50012,868.94975 L295.17362,965.05417 Z M1009.7413,1024 L843.46322,1024 C838.65152,1024 834.24042,1021.5941 831.56732,1017.8515 L719.69042,860.52891 C719.02212,859.45961 718.35382,858.3903 717.95292,857.18731 L662.48222,695.18653 C661.41302,691.9786 658.87342,689.17164 655.66532,687.56767 L519.86272,618.99802 C516.12012,617.12675 513.44682,613.65147 512.64482,609.77521 L492.59532,510.32918 C492.32792,508.72522 491.65962,507.12125 490.59032,505.65094 L444.47622,437.88328 C441.93662,434.14069 441.53572,429.59611 443.40692,425.58618 L471.47632,365.57105 C473.61502,361.02648 478.29332,357.95219 483.63972,357.68486 L535.76872,354.61059 C538.84292,354.47689 541.64992,353.40763 543.92232,351.53632 L582.28382,321.72925 C589.50162,316.11537 600.46222,318.52131 604.33842,326.40749 L736.53212,595.3395 C737.06672,596.54247 737.46782,597.74545 737.73502,598.94844 L754.04222,707.08262 C754.44312,709.62225 755.51232,711.89452 757.25012,713.76582 L1020.5682,1001.9454 C1028.3207,1010.4999 1021.7712,1024 1009.7413,1024 L1009.7413,1024 Z M363.20822,182.58501 C363.20822,151.97594 382.58972,125.64413 410.39192,113.34703 C408.38692,110.00543 407.18402,106.39651 407.18402,102.52025 C407.18402,87.683545 424.29302,75.653785 445.27822,75.653785 L446.74862,75.653785 C455.43662,61.218065 472.01102,51.460605 490.99122,51.460605 C492.32792,51.460605 493.66452,51.460605 495.00122,51.594305 C496.73892,51.727995 498.47652,50.925975 499.41212,49.589335 C513.44682,28.203095 549.00152,12.965395 590.57112,12.965395 C605.94232,12.965395 620.51172,15.104025 633.47712,18.712955 C636.55152,13.633715 643.36822,10.158455 651.25442,10.158455 C660.21002,10.158455 667.82882,14.703035 670.10102,20.985235 C681.06162,8.287145 699.64082,-4.99999999e-06 720.75972,-4.99999999e-06 C754.44312,-4.99999999e-06 781.71052,21.252565 781.71052,47.450715 C781.71052,50.658645 781.30952,53.732915 780.50752,56.807185 C779.97292,58.945825 781.17582,61.084435 783.44822,61.886425 C804.96812,69.772605 819.53752,84.742975 819.53752,101.85196 C819.53752,121.36691 800.69092,138.07492 774.09172,144.62445 C771.95302,145.15911 770.61642,147.0304 770.61642,149.03537 L770.61642,149.43635 C770.61642,164.54039 755.64602,176.97115 736.39842,178.30779 C736.53172,178.97612 736.53172,179.64442 736.53172,180.44641 C736.53172,209.4515 681.32862,232.84271 613.29352,232.84271 C598.59062,232.84271 584.55582,231.77339 571.59042,229.76844 L571.59042,230.43676 C571.59042,242.46651 556.08532,252.22399 537.10502,252.22399 C536.03582,252.22399 535.10012,252.22399 534.16452,252.0903 C535.50122,255.0309 536.16952,258.10517 536.16952,261.31311 C536.16952,280.02607 512.51092,295.1301 483.23842,295.1301 C480.03052,295.1301 476.95632,294.9964 473.88212,294.59544 C471.47602,294.32813 469.20382,295.79843 468.66902,298.07073 C466.12942,307.4272 457.97602,314.24406 448.21862,314.24406 C436.45612,314.24406 427.09962,304.21926 427.09962,291.92217 C427.09962,286.17462 429.10462,280.96172 432.44622,277.08546 C433.64922,275.74883 434.05012,273.87753 433.24812,272.27355 C431.37682,268.79829 430.57482,265.18936 430.57482,261.31311 C430.57482,259.17449 428.83722,257.43685 426.69852,256.90221 C390.47572,248.88236 363.20822,218.67429 363.20822,182.58501 L363.20822,182.58501 Z M670.10102,908.64795 C670.63582,910.25193 670.76932,911.85591 670.76932,913.59353 L663.01682,1011.5693 C662.48222,1018.5198 656.33362,1024 648.84852,1024 L398.62952,1024 C393.28292,1024 388.33742,1020.9257 385.93132,1016.5148 L344.62922,939.79168 C344.36192,939.39068 344.22822,938.98969 344.09452,938.5887 L305.19832,844.35557 C303.72812,841.01397 303.99532,837.13772 305.59942,833.92976 L370.15902,707.61727 C371.76302,704.543 372.03022,700.93407 370.82732,697.72613 L339.28262,610.30987 C338.21332,607.23559 338.34702,603.76032 339.68372,600.81972 L392.34732,488.6756 C397.69372,477.44782 415.20382,478.38348 418.94642,490.14591 L435.38712,541.20556 L485.51112,675.40424 C486.84782,679.14684 490.05572,682.08744 494.06562,683.42409 L600.99682,719.91436 C605.14032,721.38468 608.34842,724.45894 609.68492,728.3352 L670.10102,908.64795 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
9
wulkanowy/Assets.xcassets/wulkanowy.imageset/wulkanowy.svg
vendored
Normal file
9
wulkanowy/Assets.xcassets/wulkanowy.imageset/wulkanowy.svg
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Shape</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="wulkanowy" fill="#000000" fill-rule="nonzero">
|
||||
<path d="M295.17362,965.05417 C296.24282,968.52944 295.70822,972.27203 293.83692,975.34631 L268.03972,1017.1831 C265.50012,1021.3267 260.82192,1024 255.74272,1024 L14.345318,1024 C3.1176178,1024 -3.6991822,1012.2376 2.3157178,1003.4158 L157.76692,774.44928 C158.70252,773.11265 159.23732,771.64234 159.63822,770.17205 L230.88102,465.95272 C231.68312,462.34379 234.08912,459.26952 237.56432,457.39823 L334.06972,404.46727 C337.54502,402.59597 339.95092,399.5217 340.75292,395.77911 L353.71832,339.23923 C356.39162,327.47681 373.23342,324.93718 379.91642,335.22932 L384.59472,342.71451 C386.59962,345.92244 387.13432,349.93236 385.79782,353.54129 L297.84672,607.77024 C297.17842,609.77521 296.91122,611.91384 297.31212,614.05247 L319.23302,735.68673 C319.63402,738.09268 319.36642,740.63229 318.29732,742.90458 L266.03482,860.26158 C264.83182,863.06854 264.56442,866.14281 265.50012,868.94975 L295.17362,965.05417 Z M1009.7413,1024 L843.46322,1024 C838.65152,1024 834.24042,1021.5941 831.56732,1017.8515 L719.69042,860.52891 C719.02212,859.45961 718.35382,858.3903 717.95292,857.18731 L662.48222,695.18653 C661.41302,691.9786 658.87342,689.17164 655.66532,687.56767 L519.86272,618.99802 C516.12012,617.12675 513.44682,613.65147 512.64482,609.77521 L492.59532,510.32918 C492.32792,508.72522 491.65962,507.12125 490.59032,505.65094 L444.47622,437.88328 C441.93662,434.14069 441.53572,429.59611 443.40692,425.58618 L471.47632,365.57105 C473.61502,361.02648 478.29332,357.95219 483.63972,357.68486 L535.76872,354.61059 C538.84292,354.47689 541.64992,353.40763 543.92232,351.53632 L582.28382,321.72925 C589.50162,316.11537 600.46222,318.52131 604.33842,326.40749 L736.53212,595.3395 C737.06672,596.54247 737.46782,597.74545 737.73502,598.94844 L754.04222,707.08262 C754.44312,709.62225 755.51232,711.89452 757.25012,713.76582 L1020.5682,1001.9454 C1028.3207,1010.4999 1021.7712,1024 1009.7413,1024 L1009.7413,1024 Z M363.20822,182.58501 C363.20822,151.97594 382.58972,125.64413 410.39192,113.34703 C408.38692,110.00543 407.18402,106.39651 407.18402,102.52025 C407.18402,87.683545 424.29302,75.653785 445.27822,75.653785 L446.74862,75.653785 C455.43662,61.218065 472.01102,51.460605 490.99122,51.460605 C492.32792,51.460605 493.66452,51.460605 495.00122,51.594305 C496.73892,51.727995 498.47652,50.925975 499.41212,49.589335 C513.44682,28.203095 549.00152,12.965395 590.57112,12.965395 C605.94232,12.965395 620.51172,15.104025 633.47712,18.712955 C636.55152,13.633715 643.36822,10.158455 651.25442,10.158455 C660.21002,10.158455 667.82882,14.703035 670.10102,20.985235 C681.06162,8.287145 699.64082,-4.99999999e-06 720.75972,-4.99999999e-06 C754.44312,-4.99999999e-06 781.71052,21.252565 781.71052,47.450715 C781.71052,50.658645 781.30952,53.732915 780.50752,56.807185 C779.97292,58.945825 781.17582,61.084435 783.44822,61.886425 C804.96812,69.772605 819.53752,84.742975 819.53752,101.85196 C819.53752,121.36691 800.69092,138.07492 774.09172,144.62445 C771.95302,145.15911 770.61642,147.0304 770.61642,149.03537 L770.61642,149.43635 C770.61642,164.54039 755.64602,176.97115 736.39842,178.30779 C736.53172,178.97612 736.53172,179.64442 736.53172,180.44641 C736.53172,209.4515 681.32862,232.84271 613.29352,232.84271 C598.59062,232.84271 584.55582,231.77339 571.59042,229.76844 L571.59042,230.43676 C571.59042,242.46651 556.08532,252.22399 537.10502,252.22399 C536.03582,252.22399 535.10012,252.22399 534.16452,252.0903 C535.50122,255.0309 536.16952,258.10517 536.16952,261.31311 C536.16952,280.02607 512.51092,295.1301 483.23842,295.1301 C480.03052,295.1301 476.95632,294.9964 473.88212,294.59544 C471.47602,294.32813 469.20382,295.79843 468.66902,298.07073 C466.12942,307.4272 457.97602,314.24406 448.21862,314.24406 C436.45612,314.24406 427.09962,304.21926 427.09962,291.92217 C427.09962,286.17462 429.10462,280.96172 432.44622,277.08546 C433.64922,275.74883 434.05012,273.87753 433.24812,272.27355 C431.37682,268.79829 430.57482,265.18936 430.57482,261.31311 C430.57482,259.17449 428.83722,257.43685 426.69852,256.90221 C390.47572,248.88236 363.20822,218.67429 363.20822,182.58501 L363.20822,182.58501 Z M670.10102,908.64795 C670.63582,910.25193 670.76932,911.85591 670.76932,913.59353 L663.01682,1011.5693 C662.48222,1018.5198 656.33362,1024 648.84852,1024 L398.62952,1024 C393.28292,1024 388.33742,1020.9257 385.93132,1016.5148 L344.62922,939.79168 C344.36192,939.39068 344.22822,938.98969 344.09452,938.5887 L305.19832,844.35557 C303.72812,841.01397 303.99532,837.13772 305.59942,833.92976 L370.15902,707.61727 C371.76302,704.543 372.03022,700.93407 370.82732,697.72613 L339.28262,610.30987 C338.21332,607.23559 338.34702,603.76032 339.68372,600.81972 L392.34732,488.6756 C397.69372,477.44782 415.20382,478.38348 418.94642,490.14591 L435.38712,541.20556 L485.51112,675.40424 C486.84782,679.14684 490.05572,682.08744 494.06562,683.42409 L600.99682,719.91436 C605.14032,721.38468 608.34842,724.45894 609.68492,728.3352 L670.10102,908.64795 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
|
@ -7,15 +7,117 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
enum AvailableEndpoints: String, CaseIterable {
|
||||
case vulcan = "Vulcan"
|
||||
case fakelog = "Fakelog"
|
||||
}
|
||||
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@StateObject var vulcan: VulcanStore = VulcanStore.shared
|
||||
|
||||
@State private var token: String = ""
|
||||
@State private var symbol: String = ""
|
||||
@State private var pin: String = ""
|
||||
@State private var deviceModel: String = ""
|
||||
|
||||
let cellHeight: CGFloat = 55
|
||||
let cornerRadius: CGFloat = 12
|
||||
let cellBackground: Color = Color(UIColor.systemGray6).opacity(0.5)
|
||||
|
||||
private func login() {
|
||||
vulcan.login(token: token, symbol: symbol, pin: pin, deviceModel: deviceModel) { error in
|
||||
if let error = error {
|
||||
print("error: \(error)")
|
||||
} else {
|
||||
print("success")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
Text("Wulkanowy!")
|
||||
.bold()
|
||||
VStack {
|
||||
VStack {
|
||||
Image("wulkanowy")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 92)
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("loginTitle")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.padding(.top, 50)
|
||||
.padding(.bottom, -50)
|
||||
|
||||
Spacer()
|
||||
|
||||
TextField("token", text: $token)
|
||||
.autocapitalization(.none)
|
||||
.font(Font.body.weight(Font.Weight.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.background(cellBackground)
|
||||
.cornerRadius(cornerRadius)
|
||||
|
||||
TextField("symbol", text: $symbol)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(Font.body.weight(Font.Weight.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.background(cellBackground)
|
||||
.cornerRadius(cornerRadius)
|
||||
|
||||
TextField("pin", text: $pin)
|
||||
.keyboardType(.numberPad)
|
||||
.autocapitalization(.none)
|
||||
.font(Font.body.weight(Font.Weight.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.background(cellBackground)
|
||||
.cornerRadius(cornerRadius)
|
||||
|
||||
TextField("Device name", text: $deviceModel)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(Font.body.weight(Font.Weight.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.background(cellBackground)
|
||||
.cornerRadius(cornerRadius)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("loginButton") {login()}
|
||||
.font(.headline)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.accentColor.opacity(0.1))
|
||||
.cornerRadius(cornerRadius)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
Group {
|
||||
ContentView()
|
||||
|
||||
}
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Wulkanowy</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
@ -20,6 +22,11 @@
|
|||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
|
|
37
wulkanowy/VulcanStore.swift
Normal file
37
wulkanowy/VulcanStore.swift
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// VulcanStore.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 16/02/2021.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Sdk
|
||||
|
||||
final class VulcanStore: ObservableObject {
|
||||
static let shared: VulcanStore = VulcanStore()
|
||||
|
||||
let sdk: Sdk?
|
||||
private init() {
|
||||
// Check for stored certificate
|
||||
guard let certificate: X509 = try? X509(serialNumber: 1, certificateEntries: ["CN": "APP_CERTIFICATE CA Certificate"]) else {
|
||||
sdk = nil
|
||||
return
|
||||
}
|
||||
|
||||
sdk = Sdk(certificate: certificate)
|
||||
}
|
||||
|
||||
public func login(token: String, symbol: String, pin: String, deviceModel: String, completionHandler: @escaping (Error?) -> Void) {
|
||||
sdk?.login(token: token, symbol: symbol, pin: pin, deviceModel: deviceModel) { error in
|
||||
if error == nil {
|
||||
// Success - save certificate
|
||||
} else {
|
||||
// Error - discard or try again
|
||||
}
|
||||
|
||||
completionHandler(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
wulkanowy/en.lproj/Localizable.strings
Normal file
13
wulkanowy/en.lproj/Localizable.strings
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
Localizable.strings
|
||||
wulkanowy
|
||||
|
||||
Created by Tomasz on 11/02/2021.
|
||||
|
||||
*/
|
||||
|
||||
"loginTitle" = "Log In";
|
||||
"token" = "Token";
|
||||
"symbol" = "Symbol";
|
||||
"pin" = "Pin";
|
||||
"loginButton" = "Login";
|
13
wulkanowy/pl-PL.lproj/Localizable.strings
Normal file
13
wulkanowy/pl-PL.lproj/Localizable.strings
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
Localizable.strings
|
||||
wulkanowy
|
||||
|
||||
Created by Tomasz on 11/02/2021.
|
||||
|
||||
*/
|
||||
|
||||
"loginTitle" = "Logowanie";
|
||||
"token" = "Token";
|
||||
"symbol" = "Symbol";
|
||||
"pin" = "Pin";
|
||||
"loginButton" = "Zaloguj";
|
|
@ -6,6 +6,8 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import Sdk
|
||||
import Combine
|
||||
|
||||
@main
|
||||
struct wulkanowyApp: App {
|
||||
|
|
Loading…
Reference in a new issue