157 lines
4.7 KiB
Swift
157 lines
4.7 KiB
Swift
import Foundation
|
|
import Combine
|
|
import Network
|
|
|
|
enum SocketStatus: Codable {
|
|
case disconnected
|
|
case connected
|
|
}
|
|
|
|
enum SocketError: Error, Codable {
|
|
case connectionInitError
|
|
}
|
|
|
|
enum SocketAction: ModuleAction {
|
|
var moduleName: ModuleName {
|
|
"Socket"
|
|
}
|
|
|
|
case tryConnect(host: String, port: Int)
|
|
case connectionUpdated(_ status: SocketStatus)
|
|
case connectionError(_ err: SocketError)
|
|
}
|
|
|
|
struct SocketState: ModuleState {
|
|
var moduleName: ModuleName { "Socket" }
|
|
var socketStatus: SocketStatus = .disconnected
|
|
}
|
|
|
|
class SocketModule: Module, ModuleContinuousActing {
|
|
var moduleName: ModuleName { "Socket" }
|
|
var asyncDispatch: ((any ModuleAction) -> ())?
|
|
|
|
private let socketQueue = DispatchQueue.init(label: "socket.queue.\(UUID().uuidString)")
|
|
private var connection: NWConnection?
|
|
|
|
func getInitState() -> OptionalState {
|
|
SocketState.init()
|
|
}
|
|
|
|
func reduce(state: inout any ModuleState, action: any ModuleAction) {
|
|
guard let action = action as? SocketAction, var newState = state as? SocketState else {
|
|
return
|
|
}
|
|
switch action {
|
|
case .connectionUpdated(let status):
|
|
if newState.socketStatus != status {
|
|
newState.socketStatus = status
|
|
}
|
|
|
|
default:
|
|
break
|
|
}
|
|
state = newState
|
|
}
|
|
|
|
func middleware(state: GlobalState, action: any ModuleAction) -> AnyPublisher<any ModuleAction, Never> {
|
|
switch action {
|
|
case let act as SRVResolverAction:
|
|
switch act {
|
|
case .recordsFound:
|
|
if let founded = (state["Resolver"] as? SRVResolverState)?.foundedRecords, let record = founded.first {
|
|
return Just(SocketAction.tryConnect(host: record.target, port: record.port))
|
|
.eraseToAnyPublisher()
|
|
} else {
|
|
return Empty().eraseToAnyPublisher()
|
|
}
|
|
|
|
default:
|
|
return Empty().eraseToAnyPublisher()
|
|
}
|
|
|
|
case let act as SocketAction:
|
|
switch act {
|
|
case .tryConnect(let host, let port):
|
|
do {
|
|
try startConnection(host: host, port: port)
|
|
return Empty().eraseToAnyPublisher()
|
|
} catch {
|
|
return Just(SocketAction.connectionError(.connectionInitError))
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
default:
|
|
return Empty().eraseToAnyPublisher()
|
|
}
|
|
|
|
default:
|
|
return Empty().eraseToAnyPublisher()
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension SocketModule {
|
|
private func startConnection(host: String, port: Int) throws {
|
|
// options and params
|
|
let tcpOptions = NWProtocolTCP.Options()
|
|
tcpOptions.noDelay = true
|
|
tcpOptions.connectionTimeout = 5
|
|
tcpOptions.enableFastOpen = true
|
|
tcpOptions.disableAckStretching = true
|
|
let params = NWParameters(tls: nil, tcp: tcpOptions)
|
|
params.serviceClass = .responsiveData
|
|
|
|
// connection
|
|
connection = NWConnection(host: .name(host, nil), port: .init(integerLiteral: UInt16(port)), using: params)
|
|
guard let connection = connection else {
|
|
throw SocketError.connectionInitError
|
|
}
|
|
|
|
// subscribe to state
|
|
connection.stateUpdateHandler = { [weak self] state in
|
|
switch state {
|
|
case .cancelled, .setup:
|
|
self?.asyncDispatch?(SocketAction.connectionUpdated(.disconnected))
|
|
|
|
case .preparing:
|
|
self?.asyncDispatch?(SocketAction.connectionUpdated(.disconnected))
|
|
|
|
case .failed(let error):
|
|
self?.asyncDispatch?(SocketAction.connectionUpdated(.disconnected))
|
|
|
|
case .waiting(let error):
|
|
self?.asyncDispatch?(SocketAction.connectionUpdated(.disconnected))
|
|
|
|
case .ready:
|
|
self?.asyncDispatch?(SocketAction.connectionUpdated(.connected))
|
|
self?.read()
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
//
|
|
connection.start(queue: socketQueue)
|
|
}
|
|
|
|
func write(data: Data) {
|
|
connection?.send(content: data, completion: .contentProcessed({ err in
|
|
if let err = err {
|
|
print(err)
|
|
}
|
|
}))
|
|
}
|
|
|
|
private func read() {
|
|
connection?.receive(minimumIncompleteLength: 1, maximumLength: 4096 * 2, completion: { [weak self] data, _, complete, error in
|
|
if let data = data, complete == true {
|
|
print(String(bytes: data, encoding: .utf8) ?? "???")
|
|
self?.read()
|
|
} else if let err = error {
|
|
print(err)
|
|
}
|
|
})
|
|
}
|
|
}
|