using System.Buffers.Binary; using System.Net; using System.Net.Sockets; using System.Net.WebSockets; using System.Text; using System.Text.Json; using SwyxTray.Recording; using SwyxTray.Web; internal static class TransportChecks { // Local-only fixture for browser -> real SwyxWeb -> isolated SwyxTrans integration checks. public static async Task Serve() { var folder = Path.Combine(Path.GetTempPath(), "swyx-browser-check-" + Guid.NewGuid()); Directory.CreateDirectory(folder); var now = DateTime.UtcNow; using var outbox = new RecordingOutbox(Path.Combine(folder, "queue"), () => now); var id = outbox.Begin(1, 123, "123", "WebSocket-TestgesprÃĪch", folder, null); OutboxChecks.WriteWav(Path.Combine(folder, "call.wav"), now, 32_000); outbox.Observe(new SwyxTray.Swyx.SwyxSnapshot(true, true, "test", "test", [new SwyxTray.Swyx.SwyxLineInfo(0, IpPbx.CLMgrLib.CLMgrLineStates.LSInactive, "123", "Test", true, 123)])); now = now.AddSeconds(4); outbox.Process(); now = now.AddSeconds(3); outbox.Process(); using var listener = new TcpListener(IPAddress.Loopback, 17658); listener.Start(); Console.WriteLine("Synthetic WAV fixture ready: " + folder); while (true) { var tcp = await listener.AcceptTcpClientAsync(); _ = Task.Run(async () => { using (tcp) { try { var stream = tcp.GetStream(); var request = await WebSocketHandshake.ReadAsync(stream, CancellationToken.None); if (request == null) return; await WebSocketHandshake.AcceptAsync(stream, request, CancellationToken.None); using var session = new WebSocketSession(1, WebSocket.CreateFromStream(stream, true, null, TimeSpan.FromSeconds(15)), new CommandExecutor(outbox), new PluginTabChannel(), request.Origin, false); await session.RunAsync(CancellationToken.None); } catch (Exception ex) { Console.WriteLine(ex.GetType().Name); } } }); } } public static async Task Run(Action check) { var folder = Path.Combine(Path.GetTempPath(), "swyx-transport-" + Guid.NewGuid()); Directory.CreateDirectory(folder); try { var now = DateTime.UtcNow; using var outbox = new RecordingOutbox(Path.Combine(folder, "queue"), () => now); var id = outbox.Begin(1, 123, "123", "Test", folder, null); var path = Path.Combine(folder, "call.wav"); OutboxChecks.WriteWav(path, now, samples: 1_000_000); outbox.Observe(new SwyxTray.Swyx.SwyxSnapshot(true, true, "test", "test", [new SwyxTray.Swyx.SwyxLineInfo(0, IpPbx.CLMgrLib.CLMgrLineStates.LSInactive, "123", "Test", true, 123)])); now = now.AddSeconds(4); outbox.Process(); now = now.AddSeconds(3); outbox.Process(); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); for (int round = 0; round < 3; round++) { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); using var tcp = new TcpClient(); var connection = tcp.ConnectAsync((IPEndPoint)listener.LocalEndpoint); using var remote = await listener.AcceptTcpClientAsync(timeout.Token); await connection; using var socket = WebSocket.CreateFromStream(tcp.GetStream(), false, null, TimeSpan.FromSeconds(15)); using var server = new WebSocketSession(round, WebSocket.CreateFromStream(remote.GetStream(), true, null, TimeSpan.FromSeconds(15)), new CommandExecutor(outbox), new PluginTabChannel(), "http://test", false); var running = server.RunAsync(timeout.Token); var notice = await Until(socket, e => e.TryGetProperty("recordingId", out _), timeout.Token); check(notice.GetProperty("recordingId").GetGuid() == id, "recording replay uses original UUID"); if (round == 2) { check(notice.GetProperty("state").GetString() == "submitted", "receipt replay on reconnect"); } else { await Send(socket, new { id = 7, cmd = "recording.download", recordingId = id }, timeout.Token); await Send(socket, new { id = 8, cmd = "ping" }, timeout.Token); using var received = new MemoryStream(); bool done = false, ping = false; while (!done || !ping) { var (type, bytes) = await Receive(socket, timeout.Token); if (type == WebSocketMessageType.Binary) { check(bytes.AsSpan(0, 4).SequenceEqual("SWYX"u8), "binary block magic"); check(BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(4)) == 7, "binary request id"); check(BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(8)) == received.Length, "binary offset"); received.Write(bytes, 12, bytes.Length - 12); if (round == 0) break; // Interrupt, then request the same retained WAV on another session. } else { var result = JsonDocument.Parse(bytes).RootElement; if (result.TryGetProperty("id", out var requestId)) { check(result.GetProperty("ok").GetBoolean(), "transfer/control command succeeds"); if (requestId.GetInt32() == 7) done = true; if (requestId.GetInt32() == 8) ping = true; } } } if (round == 1) { check(received.ToArray().SequenceEqual(File.ReadAllBytes(path)), "actual WebSocket preserves entire WAV"); check(ping, "control commands remain responsive during transfer"); await Send(socket, new { id = 9, cmd = "recording.ack", recordingId = id, jobId = new string('a', 32) }, timeout.Token); var ack = await Until(socket, e => e.TryGetProperty("id", out var n) && n.GetInt32() == 9, timeout.Token); check(ack.GetProperty("ok").GetBoolean(), "transfer receipt accepted"); } } socket.Abort(); server.Dispose(); try { await running; } catch (Exception ex) when (ex is WebSocketException or IOException or ObjectDisposedException) { } } check(File.Exists(path), "original WAV survives websocket acknowledgements"); } finally { Directory.Delete(folder, true); } } private static Task Send(WebSocket socket, object value, CancellationToken token) => socket.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)), WebSocketMessageType.Text, true, token); private static async Task Until(WebSocket socket, Func predicate, CancellationToken token) { while (true) { var (type, bytes) = await Receive(socket, token); if (type != WebSocketMessageType.Text) continue; var element = JsonDocument.Parse(bytes).RootElement; if (predicate(element)) return element; } } private static async Task<(WebSocketMessageType, byte[])> Receive(WebSocket socket, CancellationToken token) { using var message = new MemoryStream(); var buffer = new byte[32 * 1024]; WebSocketReceiveResult result; do { result = await socket.ReceiveAsync(buffer, token); message.Write(buffer, 0, result.Count); } while (!result.EndOfMessage); return (result.MessageType, message.ToArray()); } }