// // KissConsole+WebSocket.swift // KissMeConsole // // Created by ened-book-m1 on 2023/09/04. // import Foundation import KissMe extension KissConsole { } public protocol KissAuctionPriceDelegate: AnyObject { func auction(_ auction: KissAuction, contactPrice: Domestic.ContractPrice) func auction(_ auction: KissAuction, askingPrice: Domestic.AskingPrice) } public protocol KissAutionNoticeDelegate: AnyObject { func auction(_ auction: KissAuction, contactNotice: Domestic.ContractNotice) } public class KissAuction: NSObject { public class Slot { var contractPrice: Domestic.ContractPriceWebSocket! var askingPrice: Domestic.AskingPriceWebSocket! weak var delegate: KissAuctionPriceDelegate? var contractPriceReceiver: ContractPriceReceiver! var askingPriceReceiver: AskingPriceReceiver! init(delegate: KissAuctionPriceDelegate?) { self.delegate = delegate } func setup(contractPrice: Domestic.ContractPriceWebSocket, askingPrice: Domestic.AskingPriceWebSocket) { self.contractPrice = contractPrice self.askingPrice = askingPrice } func cleanup() { contractPriceReceiver.slot = nil contractPriceReceiver.owner = nil askingPriceReceiver.slot = nil askingPriceReceiver.owner = nil } } class ContractPriceReceiver { var slot: Slot? weak var owner: KissAuction? init(slot: Slot?, owner: KissAuction) { self.slot = slot self.owner = owner } } class AskingPriceReceiver { var slot: Slot? weak var owner: KissAuction? init(slot: Slot?, owner: KissAuction) { self.slot = slot self.owner = owner } } class ContractNoticeReceiver { weak var delegate: KissAutionNoticeDelegate? weak var owner: KissAuction? init(delegate: KissAutionNoticeDelegate?, owner: KissAuction) { self.delegate = delegate self.owner = owner } } // TODO: ensure thread-safe var slots: [Slot] let loggable: Bool let account: KissAccount var contractNotice: Domestic.ContractNoticeWebSocket? var contractNoticeReceiver: ContractNoticeReceiver? public init(account: KissAccount, loggable: Bool) { self.slots = [Slot]() self.loggable = loggable self.account = account self.contractNotice = nil self.contractNoticeReceiver = nil } public func startNotice(delegate: KissAutionNoticeDelegate?) async throws -> Bool { //account.approvalKeys. guard let isMock = account.isMock else { throw GeneralError.invalidAccessToken } let approvalKey = try await account.getApprovalKey() print("notice price key: \(approvalKey)") let credential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) contractNotice = Domestic.ContractNoticeWebSocket(credential: credential, htsID: account.accountNo) contractNoticeReceiver = ContractNoticeReceiver(delegate: delegate, owner: self) try await contractNotice!.connect() let result = try await contractNotice!.subscribe() return result } public func stopNotice() async throws -> Bool { guard var contractNotice = contractNotice else { return false } let result = try await contractNotice.unsubscribe() contractNotice.disconnect() contractNoticeReceiver?.delegate = nil return result } public func addSlot(productNo: String, delegate: KissAuctionPriceDelegate?) async throws -> Slot { guard let isMock = account.isMock else { throw GeneralError.invalidAccessToken } func createContractPrice() async throws -> Domestic.ContractPriceWebSocket { let approvalKey = try await account.getApprovalKey() print("contract price key: \(approvalKey)") let credential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) let contractPrice = Domestic.ContractPriceWebSocket(credential: credential, productCode: productNo) return contractPrice } func createAskingPrice() async throws -> Domestic.AskingPriceWebSocket { let approvalKey = try await account.getApprovalKey() print("asking price key: \(approvalKey)") let credential = KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) let askingPrice = Domestic.AskingPriceWebSocket(credential: credential, productCode: productNo) return askingPrice } let slot = Slot(delegate: delegate) slot.askingPriceReceiver = AskingPriceReceiver(slot: slot, owner: self) slot.contractPriceReceiver = ContractPriceReceiver(slot: slot, owner: self) var contractPrice = try await createContractPrice() try await contractPrice.connect() let result = try await contractPrice.subscribe() guard result else { throw GeneralError.cannotSubscribeWebSocket } var askingPrice = try await createAskingPrice() try await askingPrice.connect() let result2 = try await askingPrice.subscribe() guard result2 else { throw GeneralError.cannotSubscribeWebSocket } slot.setup(contractPrice: contractPrice, askingPrice: askingPrice) slots.append(slot) return slot } public func getSlot(productNo: String) -> Slot? { guard let slot = slots.first(where: { $0.contractPrice.productCode == productNo }) else { return nil } return slot } public func removeSlot(productNo: String) async throws -> Bool { guard let slotIndex = slots.firstIndex(where: { $0.contractPrice.productCode == productNo }) else { return false } let slot = slots.remove(at: slotIndex) slot.cleanup() return true } private func getWebSocketKey() async throws -> KissWebSocketCredential { guard let isMock = account.isMock else { throw GeneralError.invalidAccessToken } if try await account.login() { let approvalKey = try await account.getApprovalKey() return KissWebSocketCredential(isMock: isMock, accountNo: account.accountNo, approvalKey: approvalKey) } throw GeneralError.cannotIssueApprovalKey } } extension KissAuction.ContractPriceReceiver: WebSocketDelegate { func webSocket(_ webSocket: WebSocket, didPingpong dateTime: String) { } func webSocket(_ webSocket: WebSocket, didReceive data: Any) { guard let owner = owner else { return } guard let data = data as? Domestic.WebSocketData else { return } switch data { case .contractPrice(let price): slot?.delegate?.auction(owner, contactPrice: price) default: break } } } extension KissAuction.AskingPriceReceiver: WebSocketDelegate { func webSocket(_ webSocket: WebSocket, didPingpong dateTime: String) { } func webSocket(_ webSocket: WebSocket, didReceive data: Any) { guard let owner = owner else { return } guard let data = data as? Domestic.WebSocketData else { return } switch data { case .askingPrice(let price): slot?.delegate?.auction(owner, askingPrice: price) default: break } } } extension KissAuction.ContractNoticeReceiver: WebSocketDelegate { func webSocket(_ webSocket: WebSocket, didPingpong dateTime: String) { } func webSocket(_ webSocket: WebSocket, didReceive data: Any) { guard let owner = owner else { return } guard let data = data as? Domestic.WebSocketData else { return } switch data { case .contractNotice(let notice): delegate?.auction(owner, contactNotice: notice) default: break } } }