From 1004ff468a6f1986fdccee9f7a1a92fa0ce9f484 Mon Sep 17 00:00:00 2001 From: ened Date: Mon, 28 Aug 2023 22:57:03 +0900 Subject: [PATCH] Add parser with pending message handling --- KissMe/Sources/Common/WebSocket.swift | 149 ++++++++----- .../Domestic.AskingPriceWebSocket.swift | 4 + .../Domestic.ContractNoticeWebSocket.swift | 4 + .../Domestic.ContractPriceWebSocket.swift | 4 + .../Realtime/Domestic.WebSocketData.swift | 4 +- .../Realtime/Domestic.WebSocketParser.swift | 42 ++++ KissMeConsole/Sources/main.swift | 134 +----------- KissMeConsole/Sources/test_websocket.swift | 197 ++++++++++++++++++ .../macos/KissMe.xcodeproj/project.pbxproj | 4 + .../KissMeConsole.xcodeproj/project.pbxproj | 4 + 10 files changed, 363 insertions(+), 183 deletions(-) create mode 100644 KissMe/Sources/Domestic/Realtime/Domestic.WebSocketParser.swift create mode 100644 KissMeConsole/Sources/test_websocket.swift diff --git a/KissMe/Sources/Common/WebSocket.swift b/KissMe/Sources/Common/WebSocket.swift index af090d7..0ee5c59 100644 --- a/KissMe/Sources/Common/WebSocket.swift +++ b/KissMe/Sources/Common/WebSocket.swift @@ -18,15 +18,26 @@ public protocol WebSocket { var session: URLSession { get } var socket: URLSessionWebSocketTask? { get set } var socketDelegate: URLSessionWebSocketDelegate? { get } - var message: String { get set } + var queue: DispatchQueue { get } + var timer: DispatchSourceTimer? { get set } + var streamContinuation: AsyncStream.Continuation? { get set } + + var parser: WebSocketParser { get } var responseDataLoggable: Bool { get } var credential: WebSocketCredential { get } var delegate: WebSocketDelegate? { get set } } +public protocol WebSocketParser { + var pendingMessage: String { get set } + func parseData(message: String) -> [Any] +} + + public protocol WebSocketDelegate: AnyObject { + func webSocket(_ webSocket: WebSocket, didPingpong dateTime: String) func webSocket(_ webSocket: WebSocket, didReceive data: Any) } @@ -83,6 +94,16 @@ extension AuthWebSocket { socket.resume() self.socket = socket + let timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue) + timer.schedule(deadline: .now(), repeating: 0.02, leeway: .nanoseconds(1_000_000_000 / 100)) + + let _self = self + timer.setEventHandler { + _self.receiveRepeating() + } + timer.resume() + self.timer = timer + continuation.resume(returning: ()) } } @@ -91,6 +112,9 @@ extension AuthWebSocket { public mutating func disconnect() { socket?.cancel(with: .normalClosure, reason: nil) socket = nil + + timer?.cancel() + timer = nil } @@ -102,14 +126,23 @@ extension AuthWebSocket { let request = Domestic.SubscriptionRequest(approvalKey: credential.approvalKey, type: .subscribed, trId: transactionId, trKey: transactionKey) let requestData = try JSONEncoder().encode(request) let requestJson = String(data: requestData, encoding: .utf8)! - print(requestJson) + + let stream = AsyncStream { continuation in + self.streamContinuation = continuation + } try await socket.send(.string(requestJson)) - let r: Domestic.SubscriptionResult = try await receive(stopString: "0|") - guard r.body.output != nil, r.body.resultCode == "0" else { - throw GeneralError.webSocketError(r.body.resultCode, r.body.messageCode, r.body.message) + + for try await response in stream { + let data = Data(response.utf8) + let r = try JSONDecoder().decode(Domestic.SubscriptionResult.self, from: data) + + guard r.body.output != nil, r.body.resultCode == "0" else { + throw GeneralError.webSocketError(r.body.resultCode, r.body.messageCode, r.body.message) + } + return true } - return true + return false } @@ -121,61 +154,81 @@ extension AuthWebSocket { let request = Domestic.SubscriptionRequest(approvalKey: credential.approvalKey, type: .unsubscribed, trId: transactionId, trKey: transactionKey) let requestData = try JSONEncoder().encode(request) let requestJson = String(data: requestData, encoding: .utf8)! - print(requestJson) + + let stream = AsyncStream { continuation in + self.streamContinuation = continuation + } try await socket.send(.string(requestJson)) - let r: Domestic.SubscriptionResult = try await receive(stopString: "0|") - guard r.body.output != nil, r.body.resultCode == "0" else { - throw GeneralError.webSocketError(r.body.resultCode, r.body.messageCode, r.body.message) + + for try await response in stream { + let data = Data(response.utf8) + let r = try JSONDecoder().decode(Domestic.SubscriptionResult.self, from: data) + + guard r.body.output != nil, r.body.resultCode == "0" else { + throw GeneralError.webSocketError(r.body.resultCode, r.body.messageCode, r.body.message) + } + return true } - return true + return false } - mutating func receive(stopString: String) async throws -> T where T: Decodable { - guard let socket = socket else { - throw GeneralError.invalidWebSocket - } + private func receiveRepeating() { + guard let socket = socket else { return } - let newMessage = try await socket.receive() - guard let string = newMessage.string else { - throw GeneralError.notStringButData + let semaphore = DispatchSemaphore(value: 0) + Task { + do { + let newMessage = try await socket.receive() + guard let messageString = newMessage.string else { + return + } + + if responseDataLoggable { + let logName = "log/\(Date().yyyyMMdd_HHmmssSSSS_forFile)_\(transactionId).json" + let logUrl = URL.currentDirectory().appending(path: logName) + try? messageString.write(toFile: logUrl.path, atomically: true, encoding: .utf8) + } + try postMessage(messageString) + + } catch { + print(error) + } + semaphore.signal() } - - if responseDataLoggable { - let logName = "log/\(Date().yyyyMMdd_HHmmssSSSS_forFile)_\(transactionId).json" - let logUrl = URL.currentDirectory().appending(path: logName) - try? string.write(toFile: logUrl.path, atomically: true, encoding: .utf8) - } - print(string) - - let fragmentData: Data - if let r = string.range(of: stopString) { - let fragment = message + string[string.startIndex ..< r.upperBound] - fragmentData = Data(fragment.utf8) - message += string[r.upperBound ..< string.endIndex] - print("fragment: \(fragment)") - } - else { - let fragment = message + string - fragmentData = Data(fragment.utf8) - } - - let t = try JSONDecoder().decode(T.self, from: fragmentData) - return t + semaphore.wait() } - public func receive() async throws -> String? { - guard let socket = socket else { - throw GeneralError.invalidWebSocket - } - - let message = try await socket.receive() - let str = message.string + private func postMessage(_ message: String) throws { print(message) - return str + let dataArray = parser.parseData(message: message) + for data in dataArray { + if let data = data as? Domestic.WebSocketResult, case .json(let string) = data { + let data = Data(string.utf8) + let result = try JSONDecoder().decode(Domestic.GeneralResult.self, from: data) + + switch result.header.trId { + case "PINGPONG": + if let dateTime = result.header.dateTime { + delegate?.webSocket(self, didPingpong: dateTime) + } + + case "H0STCNT0", "H0STASP0", "H0STCNI0": + //let subscription = try JSONDecoder().decode(Domestic.SubscriptionResult.self, from: data) + //print("got message...") + streamContinuation?.yield(string) + streamContinuation?.finish() + + default: + assertionFailure("Unknown trId \(result.header.trId)") + } + } + + delegate?.webSocket(self, didReceive: data) + } } } diff --git a/KissMe/Sources/Domestic/Realtime/Domestic.AskingPriceWebSocket.swift b/KissMe/Sources/Domestic/Realtime/Domestic.AskingPriceWebSocket.swift index 25cce5f..4c87c37 100644 --- a/KissMe/Sources/Domestic/Realtime/Domestic.AskingPriceWebSocket.swift +++ b/KissMe/Sources/Domestic/Realtime/Domestic.AskingPriceWebSocket.swift @@ -26,6 +26,10 @@ extension Domestic { public var socketDelegate: URLSessionWebSocketDelegate? { event } public var message: String = "" public var queue: DispatchQueue = DispatchQueue(label: "kissme.asking_price_websocket") + public var timer: DispatchSourceTimer? + + public var streamContinuation: AsyncStream.Continuation? + public var parser: WebSocketParser = KissWebSocketParser() public var credential: WebSocketCredential public var delegate: WebSocketDelegate? public let transactionKey: String diff --git a/KissMe/Sources/Domestic/Realtime/Domestic.ContractNoticeWebSocket.swift b/KissMe/Sources/Domestic/Realtime/Domestic.ContractNoticeWebSocket.swift index bea2fdc..d820ac1 100644 --- a/KissMe/Sources/Domestic/Realtime/Domestic.ContractNoticeWebSocket.swift +++ b/KissMe/Sources/Domestic/Realtime/Domestic.ContractNoticeWebSocket.swift @@ -26,6 +26,10 @@ extension Domestic { public var socketDelegate: URLSessionWebSocketDelegate? { event } public var message: String = "" public var queue: DispatchQueue = DispatchQueue(label: "kissme.contact_notice_websocket") + public var timer: DispatchSourceTimer? + + public var streamContinuation: AsyncStream.Continuation? + public var parser: WebSocketParser = KissWebSocketParser() public var credential: WebSocketCredential public var delegate: WebSocketDelegate? public let transactionKey: String diff --git a/KissMe/Sources/Domestic/Realtime/Domestic.ContractPriceWebSocket.swift b/KissMe/Sources/Domestic/Realtime/Domestic.ContractPriceWebSocket.swift index 55ce59b..cf03233 100644 --- a/KissMe/Sources/Domestic/Realtime/Domestic.ContractPriceWebSocket.swift +++ b/KissMe/Sources/Domestic/Realtime/Domestic.ContractPriceWebSocket.swift @@ -26,6 +26,10 @@ extension Domestic { public var socketDelegate: URLSessionWebSocketDelegate? { event } public var message: String = "" public var queue: DispatchQueue = DispatchQueue(label: "kissme.contact_price_websocket") + public var timer: DispatchSourceTimer? + + public var streamContinuation: AsyncStream.Continuation? + public var parser: WebSocketParser = KissWebSocketParser() public var credential: WebSocketCredential public var delegate: WebSocketDelegate? public let transactionKey: String diff --git a/KissMe/Sources/Domestic/Realtime/Domestic.WebSocketData.swift b/KissMe/Sources/Domestic/Realtime/Domestic.WebSocketData.swift index 5d5546c..eebac5e 100644 --- a/KissMe/Sources/Domestic/Realtime/Domestic.WebSocketData.swift +++ b/KissMe/Sources/Domestic/Realtime/Domestic.WebSocketData.swift @@ -606,7 +606,7 @@ extension Domestic.MarketOperationCode { extension Domestic.WebSocketResult { - static public func parse(_ str: String) throws -> [Domestic.WebSocketResult] { + static public func parse(_ str: String) throws -> ([Domestic.WebSocketResult], String.Index) { var dataArray = [Domestic.WebSocketResult]() var nextAt = str.startIndex let charset = CharacterSet(charactersIn: "{}") @@ -662,7 +662,7 @@ extension Domestic.WebSocketResult { } } - return dataArray + return (dataArray, nextAt) } diff --git a/KissMe/Sources/Domestic/Realtime/Domestic.WebSocketParser.swift b/KissMe/Sources/Domestic/Realtime/Domestic.WebSocketParser.swift new file mode 100644 index 0000000..ed1015b --- /dev/null +++ b/KissMe/Sources/Domestic/Realtime/Domestic.WebSocketParser.swift @@ -0,0 +1,42 @@ +// +// Domestic.WebSocketParser.swift +// KissMe +// +// Created by ened-book-m1 on 2023/08/27. +// + +import Foundation + + +extension Domestic { + + class KissWebSocketParser: WebSocketParser { + var pendingMessage: String + + init() { + pendingMessage = "" + } + + func parseData(message: String) -> [Any] { + pendingMessage += message + + do { + let (results, endAt) = try Domestic.WebSocketResult.parse(pendingMessage) + pendingMessage = String(pendingMessage[endAt ..< pendingMessage.endIndex]) + return results + + } catch { + switch error { + case GeneralError.notEnoughWebSocketData: + // TODO: work more exception handling + break + + default: + break + } + } + + return [] + } + } +} diff --git a/KissMeConsole/Sources/main.swift b/KissMeConsole/Sources/main.swift index be6836a..a0b46a6 100644 --- a/KissMeConsole/Sources/main.swift +++ b/KissMeConsole/Sources/main.swift @@ -14,136 +14,4 @@ import KissMe //test_get_websocket_key_and_contact_price() //test_get_websocket_key_and_asking_price() //test_parse_contact_price_response() - - -func test_parse_contact_price_response() { - let str = "{\"header\":{\"tr_id\":\"H0STCNT0\",\"tr_key\":\"005930\",\"encrypt\":\"N\"},\"body\":{\"rt_cd\":\"0\",\"msg_cd\":\"OPSP0000\",\"msg1\":\"SUBSCRIBE SUCCESS\",\"output\":{\"iv\":\"dcc3c442acfb8b9a\",\"key\":\"vcvxscahuklwkiawiuxbsfcmsulqjejf\"}}}0|H0STCNT0|001|005930^134305^68100^2^1000^1.49^68305.67^68300^68700^67900^68100^68000^1^11393808^778261604700^32559^26679^-5880^79.02^6084367^4807987^1^0.43^119.32^090027^5^-200^091809^5^-600^113615^2^200^20230824^20^N^309354^354766^2143494^2041321^0.19^6698642^170.09^0^^68300{\"header\":{\"tr_id\":\"PINGPONG\",\"datetime\":\"20230824212922\"}}" - - do { - let dataArray = try Domestic.WebSocketResult.parse(str) - for data in dataArray { - switch data { - case .json(let str): - print("json: \(str)") - case .contractPrice(let price): - print("contractPrice: \(price)") - case .askingPrice(let price): - print("askingPrice: \(price)") - case .contractNotice(let notice): - print("contractNotice: \(notice)") - } - } - } catch { - print(error) - } -} - - -func test_get_websocket_key_and_asking_price() { - let isMock = false - - let semaphore = DispatchSemaphore(value: 0) - Task { - guard let (account, approvalKey) = await test_get_websocket_key(isMock: isMock) else { - return - } - - let webSocketCredential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) - - var socket = Domestic.AskingPriceWebSocket(credential: webSocketCredential, productCode: KissConsole.defaultProductNo) - - do { - try await socket.connect() - let result = try await socket.subscribe() - print(result) - - try await Task.sleep(nanoseconds: 1_000_000_000 * 3) - let result2 = try await socket.unsubscribe() - print(result2) - - if let message = try await socket.receive() { - print(message) - } - - try await Task.sleep(nanoseconds: 1_000_000_000 * 3) - socket.disconnect() - - try await Task.sleep(nanoseconds: 1_000_000_000 * 1) - } catch { - print(error) - } - - semaphore.signal() - } - semaphore.wait() -} - - -func test_get_websocket_key_and_contact_price() { - let isMock = false - - let semaphore = DispatchSemaphore(value: 0) - Task { - guard let (account, approvalKey) = await test_get_websocket_key(isMock: isMock) else { - return - } - - let webSocketCredential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) - - var socket = Domestic.ContractPriceWebSocket(credential: webSocketCredential, productCode: KissConsole.defaultProductNo) - - do { - try await socket.connect() - let result = try await socket.subscribe() - print(result) - - try await Task.sleep(nanoseconds: 1_000_000_000 * 3) - let result2 = try await socket.unsubscribe() - print(result2) - - if let message = try await socket.receive() { - print(message) - } - - try await Task.sleep(nanoseconds: 1_000_000_000 * 3) - socket.disconnect() - - try await Task.sleep(nanoseconds: 1_000_000_000 * 1) - } catch { - print(error) - } - - semaphore.signal() - } - semaphore.wait() -} - - -func test_get_websocket_key(isMock: Bool) async -> (KissAccount, String)? { - let credential: Credential - - do { - credential = try KissCredential(isMock: isMock) - } catch { - print(error) - return nil - } - - let account = KissAccount(credential: credential) - do { - /// Return existing valid key - if let approvalKey = account.approvalKey { - return (account, approvalKey) - } - - if try await account.login() { - let approvalKey = try await account.getApprovalKey() - print("approvalKey : \(approvalKey)") - return (account, approvalKey) - } - } catch { - print(error) - return nil - } - return nil -} +test_websocket_dump_data() diff --git a/KissMeConsole/Sources/test_websocket.swift b/KissMeConsole/Sources/test_websocket.swift new file mode 100644 index 0000000..d803d23 --- /dev/null +++ b/KissMeConsole/Sources/test_websocket.swift @@ -0,0 +1,197 @@ +// +// test_websocket.swift +// KissMeConsole +// +// Created by ened-book-m1 on 2023/08/26. +// + +import Foundation +import KissMe + + +class DumpWebSocketData: NSObject { + +} + + +extension DumpWebSocketData: WebSocketDelegate { + + func webSocket(_ webSocket: KissMe.WebSocket, didPingpong dateTime: String) { + } + + func webSocket(_ webSocket: WebSocket, didReceive data: Any) { + guard let data = data as? Domestic.WebSocketData else { return } + + switch data { + case .contractPrice(let price): + print("contractPrice \(price.shortCode)") + case .askingPrice(let price): + print("askingPrice \(price.shortCode)") + case .contractNotice(let notice): + print("askingPrice \(notice.shortCode)") + } + } +} + + +func test_websocket_dump_data() { + let isMock = false + + let dumpData = DumpWebSocketData() + let semaphore = DispatchSemaphore(value: 0) + Task { + guard let (account, approvalKey) = await test_get_websocket_key(isMock: isMock) else { + return + } + + let webSocketCredential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) + + var socket = Domestic.ContractPriceWebSocket(credential: webSocketCredential, productCode: KissConsole.defaultProductNo) + socket.delegate = dumpData + + do { + try await socket.connect() + let result = try await socket.subscribe() + print(result) + + try await Task.sleep(nanoseconds: 1_000_000_000 * 3) + let result2 = try await socket.unsubscribe() + print(result2) + + try await Task.sleep(nanoseconds: 1_000_000_000 * 3) + socket.disconnect() + + try await Task.sleep(nanoseconds: 1_000_000_000 * 1) + } catch { + print(error) + } + + semaphore.signal() + } + semaphore.wait() +} + + +func test_parse_contact_price_response() { + let str = "{\"header\":{\"tr_id\":\"H0STCNT0\",\"tr_key\":\"005930\",\"encrypt\":\"N\"},\"body\":{\"rt_cd\":\"0\",\"msg_cd\":\"OPSP0000\",\"msg1\":\"SUBSCRIBE SUCCESS\",\"output\":{\"iv\":\"dcc3c442acfb8b9a\",\"key\":\"vcvxscahuklwkiawiuxbsfcmsulqjejf\"}}}0|H0STCNT0|001|005930^134305^68100^2^1000^1.49^68305.67^68300^68700^67900^68100^68000^1^11393808^778261604700^32559^26679^-5880^79.02^6084367^4807987^1^0.43^119.32^090027^5^-200^091809^5^-600^113615^2^200^20230824^20^N^309354^354766^2143494^2041321^0.19^6698642^170.09^0^^68300{\"header\":{\"tr_id\":\"PINGPONG\",\"datetime\":\"20230824212922\"}}" + + do { + let (dataArray, _) = try Domestic.WebSocketResult.parse(str) + for data in dataArray { + switch data { + case .json(let str): + print("json: \(str)") + case .contractPrice(let price): + print("contractPrice: \(price)") + case .askingPrice(let price): + print("askingPrice: \(price)") + case .contractNotice(let notice): + print("contractNotice: \(notice)") + } + } + } catch { + print(error) + } +} + + +func test_get_websocket_key_and_asking_price() { + let isMock = false + + let semaphore = DispatchSemaphore(value: 0) + Task { + guard let (account, approvalKey) = await test_get_websocket_key(isMock: isMock) else { + return + } + + let webSocketCredential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) + + var socket = Domestic.AskingPriceWebSocket(credential: webSocketCredential, productCode: KissConsole.defaultProductNo) + + do { + try await socket.connect() + let result = try await socket.subscribe() + print(result) + + try await Task.sleep(nanoseconds: 1_000_000_000 * 3) + let result2 = try await socket.unsubscribe() + print(result2) + + try await Task.sleep(nanoseconds: 1_000_000_000 * 3) + socket.disconnect() + + try await Task.sleep(nanoseconds: 1_000_000_000 * 1) + } catch { + print(error) + } + + semaphore.signal() + } + semaphore.wait() +} + + +func test_get_websocket_key_and_contact_price() { + let isMock = false + + let semaphore = DispatchSemaphore(value: 0) + Task { + guard let (account, approvalKey) = await test_get_websocket_key(isMock: isMock) else { + return + } + + let webSocketCredential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) + + var socket = Domestic.ContractPriceWebSocket(credential: webSocketCredential, productCode: KissConsole.defaultProductNo) + + do { + try await socket.connect() + let result = try await socket.subscribe() + print(result) + + try await Task.sleep(nanoseconds: 1_000_000_000 * 3) + let result2 = try await socket.unsubscribe() + print(result2) + + try await Task.sleep(nanoseconds: 1_000_000_000 * 3) + socket.disconnect() + + try await Task.sleep(nanoseconds: 1_000_000_000 * 1) + } catch { + print(error) + } + + semaphore.signal() + } + semaphore.wait() +} + + +func test_get_websocket_key(isMock: Bool) async -> (KissAccount, String)? { + let credential: Credential + + do { + credential = try KissCredential(isMock: isMock) + } catch { + print(error) + return nil + } + + let account = KissAccount(credential: credential) + do { + /// Return existing valid key + if let approvalKey = account.approvalKey { + return (account, approvalKey) + } + + if try await account.login() { + let approvalKey = try await account.getApprovalKey() + print("approvalKey : \(approvalKey)") + return (account, approvalKey) + } + } catch { + print(error) + return nil + } + return nil +} diff --git a/projects/macos/KissMe.xcodeproj/project.pbxproj b/projects/macos/KissMe.xcodeproj/project.pbxproj index 37c20eb..fef11da 100644 --- a/projects/macos/KissMe.xcodeproj/project.pbxproj +++ b/projects/macos/KissMe.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ 34DA3E9D2A9A028200BB3439 /* Domestic.WebSocketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DA3E9C2A9A028200BB3439 /* Domestic.WebSocketData.swift */; }; 34DA3E9F2A9A0B8D00BB3439 /* Domestic.WebSocketAES.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DA3E9E2A9A0B8D00BB3439 /* Domestic.WebSocketAES.swift */; }; 34DA3EA22A9A10A100BB3439 /* Crypto in Frameworks */ = {isa = PBXBuildFile; productRef = 34DA3EA12A9A10A100BB3439 /* Crypto */; }; + 34DA3EA62A9B1D7500BB3439 /* Domestic.WebSocketParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DA3EA52A9B1D7500BB3439 /* Domestic.WebSocketParser.swift */; }; 34E7B9112A49BD2800B3AB9F /* DomesticIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E7B9102A49BD2800B3AB9F /* DomesticIndex.swift */; }; 34EC4D212A7ACB07002F947C /* CompanyPartitionMergerDecisionResult.json in Resources */ = {isa = PBXBuildFile; fileRef = 34EC4D202A7ACB06002F947C /* CompanyPartitionMergerDecisionResult.json */; }; 34EC4D242A7F27A8002F947C /* CompanyPartitionDecisionResult.json in Resources */ = {isa = PBXBuildFile; fileRef = 34EC4D232A7F27A8002F947C /* CompanyPartitionDecisionResult.json */; }; @@ -200,6 +201,7 @@ 34D3680E2A2AA0BE005E6756 /* PropertyIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyIterable.swift; sourceTree = ""; }; 34DA3E9C2A9A028200BB3439 /* Domestic.WebSocketData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Domestic.WebSocketData.swift; sourceTree = ""; }; 34DA3E9E2A9A0B8D00BB3439 /* Domestic.WebSocketAES.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Domestic.WebSocketAES.swift; sourceTree = ""; }; + 34DA3EA52A9B1D7500BB3439 /* Domestic.WebSocketParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Domestic.WebSocketParser.swift; sourceTree = ""; }; 34E7B9102A49BD2800B3AB9F /* DomesticIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticIndex.swift; sourceTree = ""; }; 34EC4D202A7ACB06002F947C /* CompanyPartitionMergerDecisionResult.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CompanyPartitionMergerDecisionResult.json; sourceTree = ""; }; 34EC4D232A7F27A8002F947C /* CompanyPartitionDecisionResult.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CompanyPartitionDecisionResult.json; sourceTree = ""; }; @@ -415,6 +417,7 @@ isa = PBXGroup; children = ( 34DA3E9C2A9A028200BB3439 /* Domestic.WebSocketData.swift */, + 34DA3EA52A9B1D7500BB3439 /* Domestic.WebSocketParser.swift */, 34DA3E9E2A9A0B8D00BB3439 /* Domestic.WebSocketAES.swift */, 34BC447C2A86635A0052D8EB /* Domestic.ContractPriceWebSocket.swift */, 34BC447A2A8663430052D8EB /* Domestic.AskingPriceWebSocket.swift */, @@ -824,6 +827,7 @@ 3435A7F72A35D82000D604F1 /* DomesticShortSelling.swift in Sources */, 34C1BA552A5B033E00423D64 /* DomesticDartListedCompany.swift in Sources */, 341F5F072A14634F00962D48 /* Foundation+Extensions.swift in Sources */, + 34DA3EA62A9B1D7500BB3439 /* Domestic.WebSocketParser.swift in Sources */, 341F5F032A11A2BC00962D48 /* Credential.swift in Sources */, 34BC44792A8657D50052D8EB /* WebSocket.swift in Sources */, 341F5EB02A0A80EC00962D48 /* KissMe.docc in Sources */, diff --git a/projects/macos/KissMeConsole.xcodeproj/project.pbxproj b/projects/macos/KissMeConsole.xcodeproj/project.pbxproj index f3a280e..55b6a43 100644 --- a/projects/macos/KissMeConsole.xcodeproj/project.pbxproj +++ b/projects/macos/KissMeConsole.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 349327F72A20E3E300097063 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349327F62A20E3E300097063 /* Foundation+Extensions.swift */; }; 349843212A242AC900E85B08 /* KissConsole+CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349843202A242AC900E85B08 /* KissConsole+CSV.swift */; }; 34D3680D2A280801005E6756 /* KissConsole+Candle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D3680C2A280801005E6756 /* KissConsole+Candle.swift */; }; + 34DA3EA42A9A176B00BB3439 /* test_websocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DA3EA32A9A176B00BB3439 /* test_websocket.swift */; }; 34EC4D1F2A7A7365002F947C /* KissConsole+News.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34EC4D1E2A7A7365002F947C /* KissConsole+News.swift */; }; 34EE76862A1C391B009761D2 /* KissMe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 341F5EDB2A0A8C4600962D48 /* KissMe.framework */; }; 34EE76872A1C391B009761D2 /* KissMe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 341F5EDB2A0A8C4600962D48 /* KissMe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -58,6 +59,7 @@ 3498431E2A24287600E85B08 /* KissMeConsoleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KissMeConsoleTests.swift; sourceTree = ""; }; 349843202A242AC900E85B08 /* KissConsole+CSV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+CSV.swift"; sourceTree = ""; }; 34D3680C2A280801005E6756 /* KissConsole+Candle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+Candle.swift"; sourceTree = ""; }; + 34DA3EA32A9A176B00BB3439 /* test_websocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test_websocket.swift; sourceTree = ""; }; 34EC4D1E2A7A7365002F947C /* KissConsole+News.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+News.swift"; sourceTree = ""; }; 34F190122A4441F00068C697 /* KissConsole+Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KissConsole+Test.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -97,6 +99,7 @@ children = ( 341F5ED32A0A8B9000962D48 /* main.swift */, 341F5F042A13B82F00962D48 /* test.swift */, + 34DA3EA32A9A176B00BB3439 /* test_websocket.swift */, 348168482A2F92AC00A50BD3 /* KissContext.swift */, 341F5F082A1463A100962D48 /* KissConsole.swift */, 34D3680C2A280801005E6756 /* KissConsole+Candle.swift */, @@ -196,6 +199,7 @@ 349843212A242AC900E85B08 /* KissConsole+CSV.swift in Sources */, 348168492A2F92AC00A50BD3 /* KissContext.swift in Sources */, 3435A7F42A35B4D000D604F1 /* KissConsole+Price.swift in Sources */, + 34DA3EA42A9A176B00BB3439 /* test_websocket.swift in Sources */, 34F190132A4441F00068C697 /* KissConsole+Test.swift in Sources */, 34EC4D1F2A7A7365002F947C /* KissConsole+News.swift in Sources */, );