diff --git a/Sources/CartonKit/Server/Server.swift b/Sources/CartonKit/Server/Server.swift index 53ff60c5..743db8c2 100644 --- a/Sources/CartonKit/Server/Server.swift +++ b/Sources/CartonKit/Server/Server.swift @@ -238,9 +238,14 @@ public actor Server { let environment = head.headers["User-Agent"].compactMap(DestinationEnvironment.init).first ?? .other - let handler = await ServerWebSocketHandler( + let handler = ServerWebSocketHandler( configuration: ServerWebSocketHandler.Configuration( - onText: self.createWebSocketTextHandler(in: environment, terminal: self.configuration.terminal) + onText: { [weak self] (text) in + self?.webSocketTextHandler(text: text, environment: environment) + }, + onBinary: { [weak self] (data) in + self?.webSocketBinaryHandler(data: data) + } ) ) await self.add(connection: Connection(channel: channel)) @@ -329,50 +334,87 @@ public actor Server { } extension Server { - /// Returns a handler that responds to WebSocket messages coming from the browser. - func createWebSocketTextHandler( - in environment: DestinationEnvironment, - terminal: InteractiveWriter - ) -> @Sendable (String) -> Void { - { [weak self] text in - guard let self = self else { return } - guard - let data = text.data(using: .utf8), - let event = try? self.decoder.decode(Event.self, from: data) - else { - return - } + /// Respond to WebSocket messages coming from the browser. + nonisolated func webSocketTextHandler( + text: String, + environment: DestinationEnvironment + ) { + guard + let data = text.data(using: .utf8), + let event = try? self.decoder.decode(Event.self, from: data) + else { + return + } - switch event { - case let .stackTrace(rawStackTrace): - if let stackTrace = rawStackTrace.parsedStackTrace(in: environment) { - terminal.write("\nAn error occurred, here's a stack trace for it:\n", inColor: .red) - stackTrace.forEach { item in - terminal.write(" \(item.symbol)", inColor: .cyan) - terminal.write(" at \(item.location ?? "")\n", inColor: .gray) - } - } else { - terminal.write("\nAn error occurred, here's the raw stack trace for it:\n", inColor: .red) - terminal.write( - " Please create an issue or PR to the Carton repository\n" - + " with your browser name and this raw stack trace so\n" - + " we can add support for it: https://github.com/swiftwasm/carton\n", inColor: .gray - ) - terminal.write(rawStackTrace + "\n") + let terminal = self.configuration.terminal + + switch event { + case let .stackTrace(rawStackTrace): + if let stackTrace = rawStackTrace.parsedStackTrace(in: environment) { + terminal.write("\nAn error occurred, here's a stack trace for it:\n", inColor: .red) + stackTrace.forEach { item in + terminal.write(" \(item.symbol)", inColor: .cyan) + terminal.write(" at \(item.location ?? "")\n", inColor: .gray) } + } else { + terminal.write("\nAn error occurred, here's the raw stack trace for it:\n", inColor: .red) + terminal.write( + " Please create an issue or PR to the Carton repository\n" + + " with your browser name and this raw stack trace so\n" + + " we can add support for it: https://github.com/swiftwasm/carton\n", inColor: .gray + ) + terminal.write(rawStackTrace + "\n") + } + + case let .testRunOutput(output): + TestsParser().parse(output, terminal) + + case .testPassed: + Task { await self.stopTest(hadError: false) } + + case let .errorReport(output): + terminal.write("\nAn error occurred:\n", inColor: .red) + terminal.write(output + "\n") - case let .testRunOutput(output): - TestsParser().parse(output, terminal) + Task { await self.stopTest(hadError: true) } + } + } + + private static func decodeLines(data: Data) -> [String] { + let text = String(decoding: data, as: UTF8.self) + return text.components(separatedBy: .newlines) + } + + nonisolated func webSocketBinaryHandler(data: Data) { + let terminal = self.configuration.terminal + + if data.count < 2 { + return + } + + var kind: UInt16 = 0 + _ = withUnsafeMutableBytes(of: &kind) { (buffer) in + data.copyBytes(to: buffer, from: 0..<2) + } - case .testPassed: - Task { await self.stopTest(hadError: false) } + switch kind { + case 1001: + // stdout + let chunk = data.subdata(in: 2.. Void + var onText: @Sendable (String) -> Void + var onBinary: @Sendable (Data) -> Void } private var awaitingClose: Bool = false @@ -43,7 +45,11 @@ final class ServerWebSocketHandler: ChannelInboundHandler { var data = frame.unmaskedData let text = data.readString(length: data.readableBytes) ?? "" self.configuration.onText(text) - case .binary, .continuation, .pong: + case .binary: + let nioData = frame.unmaskedData + let data = Data(nioData.readableBytesView) + self.configuration.onBinary(data) + case .continuation, .pong: // We ignore these frames. break default: diff --git a/Tests/Fixtures/DevServerTestApp/Sources/app/main.swift b/Tests/Fixtures/DevServerTestApp/Sources/app/main.swift index f5a46acc..00bed0f3 100644 --- a/Tests/Fixtures/DevServerTestApp/Sources/app/main.swift +++ b/Tests/Fixtures/DevServerTestApp/Sources/app/main.swift @@ -1 +1,16 @@ -print("hello dev server") +#if os(WASI) +import WASILibc +typealias FILEPointer = OpaquePointer +#else +import Darwin +typealias FILEPointer = UnsafeMutablePointer +#endif + +func fputs(_ string: String, file: FILEPointer) { + _ = string.withCString { (cstr) in + fputs(cstr, file) + } +} + +fputs("hello stdout\n", file: stdout) +fputs("hello stderr\n", file: stderr) diff --git a/entrypoint/dev.ts b/entrypoint/dev.ts index 0035e70a..083bb69d 100644 --- a/entrypoint/dev.ts +++ b/entrypoint/dev.ts @@ -44,9 +44,29 @@ const startWasiTask = async () => { const wasmRunner = WasmRunner( { + onStdout(chunk) { + const kindBuffer = new ArrayBuffer(2); + new DataView(kindBuffer).setUint16(0, 1001, true); + + const buffer = new Uint8Array(2 + chunk.length); + buffer.set(new Uint8Array(kindBuffer), 0); + buffer.set(chunk, 2); + + socket.send(buffer); + }, onStdoutLine(line) { console.log(line); }, + onStderr(chunk) { + const kindBuffer = new ArrayBuffer(2); + new DataView(kindBuffer).setUint16(0, 1002, true); + + const buffer = new Uint8Array(2 + chunk.length); + buffer.set(new Uint8Array(kindBuffer), 0); + buffer.set(chunk, 2); + + socket.send(buffer); + }, onStderrLine(line) { console.error(line); } diff --git a/package-lock.json b/package-lock.json index 18935180..7a070310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2183,4 +2183,4 @@ } } } -} +} \ No newline at end of file