| 1 | #if os(macOS) |
| 2 | import Foundation |
| 3 | import Network |
| 4 | |
| 5 | final class ResizeFixtureServer: @unchecked Sendable { |
| 6 | private let listener: NWListener |
| 7 | private let queue = DispatchQueue(label: "sheets.resize.fixture") |
| 8 | private let lock = NSLock() |
| 9 | private var received: [[String: Int]] = [] |
| 10 | var requests: [[String: Int]] { lock.withLock { received } } |
| 11 | init() throws { listener = try NWListener(using: .tcp, on: .any) } |
| 12 | func start() async throws -> URL { |
| 13 | listener.newConnectionHandler = { [weak self] connection in |
| 14 | guard let self else { return } |
| 15 | connection.start(queue: self.queue) |
| 16 | self.read(connection, buffer: Data()) |
| 17 | } |
| 18 | return try await withCheckedThrowingContinuation { continuation in |
| 19 | listener.stateUpdateHandler = { [weak self] state in |
| 20 | guard let self else { return } |
| 21 | switch state { |
| 22 | case .ready: |
| 23 | self.listener.stateUpdateHandler = nil |
| 24 | continuation.resume(returning: URL(string: "http://127.0.0.1:\(self.listener.port!.rawValue)")!) |
| 25 | case .failed(let error): |
| 26 | self.listener.stateUpdateHandler = nil |
| 27 | continuation.resume(throwing: error) |
| 28 | default: break |
| 29 | } |
| 30 | } |
| 31 | listener.start(queue: queue) |
| 32 | } |
| 33 | } |
| 34 | func stop() { listener.cancel() } |
| 35 | private func read(_ connection: NWConnection, buffer: Data) { |
| 36 | connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, _, complete, error in |
| 37 | guard let self, let data, error == nil else { connection.cancel(); return } |
| 38 | var bytes = buffer; bytes.append(data) |
| 39 | guard let separator = bytes.range(of: Data("\r\n\r\n".utf8)), |
| 40 | let headers = String(data: bytes[..<separator.lowerBound], encoding: .utf8), |
| 41 | let lengthLine = headers.components(separatedBy: "\r\n").first(where: { $0.lowercased().hasPrefix("content-length:") }), |
| 42 | let length = Int(lengthLine.split(separator: ":", maxSplits: 1)[1].trimmingCharacters(in: .whitespaces)), |
| 43 | bytes.count - separator.upperBound >= length else { |
| 44 | if !complete { self.read(connection, buffer: bytes) } else { connection.cancel() } |
| 45 | return |
| 46 | } |
| 47 | let body = bytes.subdata(in: separator.upperBound..<(separator.upperBound + length)) |
| 48 | guard let request = try? JSONDecoder().decode([String: Int].self, from: body), let size = request["size"] else { connection.cancel(); return } |
| 49 | self.lock.withLock { self.received.append(request) } |
| 50 | let axis = request["col"] == nil ? "rows" : "cols" |
| 51 | let index = request["col"] ?? request["row"] ?? 0 |
| 52 | let response = "{\"version\":0,\"cells\":[],\"sizes\":{\"\(axis)\":{\"\(index)\":\(size)}}}" |
| 53 | let message = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: \(response.utf8.count)\r\nConnection: close\r\n\r\n\(response)" |
| 54 | connection.send(content: Data(message.utf8), completion: .contentProcessed { _ in connection.cancel() }) |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | #endif |
| 59 | |