diff --git a/src/BizHawk.Client.Common/Api/ClientWebSocketWrapper.cs b/src/BizHawk.Client.Common/Api/ClientWebSocketWrapper.cs
new file mode 100644
index 0000000000..2b34d1c326
--- /dev/null
+++ b/src/BizHawk.Client.Common/Api/ClientWebSocketWrapper.cs
@@ -0,0 +1,66 @@
+#nullable enable
+
+using System;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace BizHawk.Client.Common
+{
+ public struct ClientWebSocketWrapper
+ {
+ private ClientWebSocket? _w;
+
+ /// calls getter (unless closed/disposed, then is alwars returned)
+ public WebSocketState State => _w?.State ?? WebSocketState.Closed;
+
+ public ClientWebSocketWrapper(Uri uri, CancellationToken? cancellationToken = null)
+ {
+ _w = new ClientWebSocket();
+ _w.ConnectAsync(uri, cancellationToken ?? CancellationToken.None).Wait();
+ }
+
+ /// calls
+ /// also calls (wrapper property will continue to work, method calls will throw )
+ public Task Close(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken? cancellationToken = null)
+ {
+ if (_w == null) throw new ObjectDisposedException(nameof(_w));
+ var task = _w.CloseAsync(closeStatus, statusDescription, cancellationToken ?? CancellationToken.None);
+ _w?.Dispose();
+ _w = null;
+ return task;
+ }
+
+ /// calls
+ public Task Receive(ArraySegment buffer, CancellationToken? cancellationToken = null)
+ => _w?.ReceiveAsync(buffer, cancellationToken ?? CancellationToken.None)
+ ?? throw new ObjectDisposedException(nameof(_w));
+
+ /// calls
+ public string Receive(int bufferCap, CancellationToken? cancellationToken = null)
+ {
+ if (_w == null) throw new ObjectDisposedException(nameof(_w));
+ var buffer = new byte[bufferCap];
+ var result = Receive(new ArraySegment(buffer), cancellationToken ?? CancellationToken.None).Result;
+ return Encoding.UTF8.GetString(buffer, 0, result.Count);
+ }
+
+ /// calls
+ public Task Send(ArraySegment buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken? cancellationToken = null)
+ => _w?.SendAsync(buffer, messageType, endOfMessage, cancellationToken ?? CancellationToken.None)
+ ?? throw new ObjectDisposedException(nameof(_w));
+
+ /// calls
+ public Task Send(string message, bool endOfMessage, CancellationToken? cancellationToken = null)
+ {
+ if (_w == null) throw new ObjectDisposedException(nameof(_w));
+ return Send(
+ new ArraySegment(Encoding.UTF8.GetBytes(message)),
+ WebSocketMessageType.Text,
+ endOfMessage,
+ cancellationToken ?? CancellationToken.None
+ );
+ }
+ }
+}
diff --git a/src/BizHawk.Client.Common/Api/Interfaces/ICommApi.cs b/src/BizHawk.Client.Common/Api/Interfaces/ICommApi.cs
index e5ea358be1..6dab4d8ad7 100644
--- a/src/BizHawk.Client.Common/Api/Interfaces/ICommApi.cs
+++ b/src/BizHawk.Client.Common/Api/Interfaces/ICommApi.cs
@@ -10,6 +10,8 @@ namespace BizHawk.Client.Common
SocketServer? Sockets { get; }
+ WebSocketServer? WebSockets { get; }
+
string? HttpTest();
string? HttpTestGet();
diff --git a/src/BizHawk.Client.Common/Api/WebSocketClient.cs b/src/BizHawk.Client.Common/Api/WebSocketClient.cs
deleted file mode 100644
index 3bdcd2bbd2..0000000000
--- a/src/BizHawk.Client.Common/Api/WebSocketClient.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Net.WebSockets;
-using System.Text;
-using System.Threading;
-
-namespace BizHawk.Client.Common
-{
- public class WebSocketClient
- {
- private Dictionary activeSockets;
-
- public WebSocketClient()
- {
- activeSockets = new Dictionary();
- }
-
- public string OpenSocket(string url)
- {
- var ws = new ClientWebSocket();
- var id = new Guid().ToString();
-
- ws.ConnectAsync(new Uri(url), CancellationToken.None).Wait();
-
- activeSockets[id] = ws;
-
- return id;
- }
-
- public void SendToSocket(string id, string content, bool endOfMessage)
- {
- var ws = activeSockets[id];
- var msg = new ArraySegment(Encoding.UTF8.GetBytes(content));
-
- ws.SendAsync(msg, WebSocketMessageType.Text, endOfMessage, CancellationToken.None);
- }
-
- public string ReceiveFromSocket(string id, int maxRead)
- {
- var ws = activeSockets[id];
-
- var rcvBytes = new byte[maxRead];
- var rcvBuffer = new ArraySegment(rcvBytes);
-
- var result = ws.ReceiveAsync(rcvBuffer, CancellationToken.None).Result;
- string rcvMsg = Encoding.UTF8.GetString(rcvBuffer.Take(result.Count).ToArray());
-
- return rcvMsg;
- }
-
- public int GetSocketStatus(string id)
- {
- var ws = activeSockets[id];
-
- return (int)ws.State;
- }
-
- public void CloseSocket(string id, int status, string closeMessage)
- {
- var ws = activeSockets[id];
-
- ws.CloseAsync((WebSocketCloseStatus)status, closeMessage, CancellationToken.None);
- }
- }
-}
\ No newline at end of file
diff --git a/src/BizHawk.Client.Common/Api/WebSocketServer.cs b/src/BizHawk.Client.Common/Api/WebSocketServer.cs
new file mode 100644
index 0000000000..da6a34a510
--- /dev/null
+++ b/src/BizHawk.Client.Common/Api/WebSocketServer.cs
@@ -0,0 +1,12 @@
+#nullable enable
+
+using System;
+using System.Threading;
+
+namespace BizHawk.Client.Common
+{
+ public sealed class WebSocketServer
+ {
+ public ClientWebSocketWrapper Open(Uri uri, CancellationToken? cancellationToken = null) => new ClientWebSocketWrapper(uri, cancellationToken);
+ }
+}
diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/CommLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/CommLuaLibrary.cs
index 3561a02301..9cf3bab09f 100644
--- a/src/BizHawk.Client.Common/lua/CommonLibs/CommLuaLibrary.cs
+++ b/src/BizHawk.Client.Common/lua/CommonLibs/CommLuaLibrary.cs
@@ -1,5 +1,8 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
+using System.Diagnostics;
+using System.Net.WebSockets;
using System.Text;
using NLua;
@@ -9,6 +12,8 @@ namespace BizHawk.Client.Common
[Description("A library for communicating with other programs")]
public sealed class CommLuaLibrary : DelegatingLuaLibrary
{
+ private readonly IDictionary _websockets = new Dictionary();
+
public CommLuaLibrary(Lua lua)
: base(lua) { }
@@ -237,41 +242,6 @@ namespace BizHawk.Client.Common
return APIs.Comm.HTTP?.GetUrl;
}
- //[LuaMethodExample("local ws = bizsocket.open('wss://echo.websocket.org');")]
- //[LuaMethod("websocket_open", "Opens a websocket and returns the id so that it can be retrieved later.")]
- //public string OpenSocket(string url)
- //{
- // return APIs.Comm.WebSocketClient?.OpenSocket(url);
- //}
-
- //[LuaMethodExample("local ws = bizsocket.send(ws_id, 'some message', true);")]
- //[LuaMethod("websocket_send", "Send a message to a certain websocket id (boolean flag endOfMessage)")]
- //public void SendToSocket(string id, string content, bool endOfMessage)
- //{
- // APIs.Comm.WebSocketClient?.SendToSocket(id, content, endOfMessage);
- //}
-
- //[LuaMethodExample("local ws = bizsocket.receive(ws_id, max_read);")]
- //[LuaMethod("websocket_receive", "Receive a message from a certain websocket id and a maximum number of bytes to read.")]
- //public string ReceiveFromSocket(string id, int maxRead)
- //{
- // return APIs.Comm.WebSocketClient?.ReceiveFromSocket(id, maxRead);
- //}
-
- //[LuaMethodExample("local ws_status = bizsocket.getstatus(ws_id);")]
- //[LuaMethod("websocket_getStatus", "Get a websocket's status")]
- //public int? GetSocketStatus(string id)
- //{
- // return APIs.Comm.WebSocketClient?.GetSocketStatus(id);
- //}
-
- //[LuaMethodExample("local ws_status = bizsocket.close(ws_id, close_status);")]
- //[LuaMethod("websocket_close", "Close a websocket connection with a close status, defined in section 11.7 of the web sockets protocol spec.")]
- //public void CloseSocket(string id, int status, string closeMessage)
- //{
- // APIs.Comm.WebSocketClient?.CloseSocket(id, status, closeMessage);
- //}
-
private void CheckHttp()
{
if (APIs.Comm.HTTP == null)
@@ -280,12 +250,45 @@ namespace BizHawk.Client.Common
}
}
- //private void CheckWebSocket()
- //{
- // if (APIs.Comm.WebSocketClient == null)
- // {
- // Log("WebSocketClient was not initialized");
- // }
- //}
+ [LuaMethod("ws_open", "Opens a websocket and returns the id so that it can be retrieved later.")]
+ [LuaMethodExample("local ws_id = comm.ws_open(\"wss://echo.websocket.org\");")]
+ public string WebSocketOpen(string uri)
+ {
+ var wsServer = APIs.Comm.WebSockets;
+ if (wsServer == null)
+ {
+ Log("WebSocket server is somehow not available");
+ return null;
+ }
+ var guid = new Guid();
+ _websockets[guid] = wsServer.Open(new Uri(uri));
+ return guid.ToString();
+ }
+
+ [LuaMethod("ws_send", "Send a message to a certain websocket id (boolean flag endOfMessage)")]
+ [LuaMethodExample("local ws = comm.ws_send(ws_id, \"some message\", true);")]
+ public void WebSocketSend(string guid, string content, bool endOfMessage)
+ {
+ if (_websockets.TryGetValue(Guid.Parse(guid), out var wrapper)) wrapper.Send(content, endOfMessage);
+ }
+
+ [LuaMethod("ws_receive", "Receive a message from a certain websocket id and a maximum number of bytes to read")]
+ [LuaMethodExample("local ws = comm.ws_receive(ws_id, str_len);")]
+ public string WebSocketReceive(string guid, int bufferCap) => _websockets.TryGetValue(Guid.Parse(guid), out var wrapper)
+ ? wrapper.Receive(bufferCap)
+ : null;
+
+ [LuaMethod("ws_get_status", "Get a websocket's status")]
+ [LuaMethodExample("local ws_status = comm.ws_get_status(ws_id);")]
+ public int? WebSocketGetStatus(string guid) => _websockets.TryGetValue(Guid.Parse(guid), out var wrapper)
+ ? (int) wrapper.State
+ : (int?) null;
+
+ [LuaMethod("ws_close", "Close a websocket connection with a close status")]
+ [LuaMethodExample("local ws_status = comm.ws_close(ws_id, close_status);")]
+ public void WebSocketClose(string guid, WebSocketCloseStatus status, string closeMessage)
+ {
+ if (_websockets.TryGetValue(Guid.Parse(guid), out var wrapper)) wrapper.Close(status, closeMessage);
+ }
}
}
\ No newline at end of file
diff --git a/src/BizHawk.Client.Common/lua/LuaHelperLibs/WebSocketLuaLibrary.cs b/src/BizHawk.Client.Common/lua/LuaHelperLibs/WebSocketLuaLibrary.cs
deleted file mode 100644
index a0556e5587..0000000000
--- a/src/BizHawk.Client.Common/lua/LuaHelperLibs/WebSocketLuaLibrary.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Net.WebSockets;
-using System.Text;
-using System.Threading;
-using NLua;
-
-
-namespace BizHawk.Client.Common.lua.LuaHelperLibs
-{
- [Description("A library exposing standard .NET string methods")]
- public sealed class WebSocketLuaLibrary : LuaLibraryBase
- {
- private Dictionary activeSockets;
-
- public WebSocketLuaLibrary(Lua lua) : base(lua)
- {
- activeSockets = new Dictionary();
- }
-
- public WebSocketLuaLibrary(Lua lua, Action logOutputCallback) : base(lua, logOutputCallback)
- {
- activeSockets = new Dictionary();
- }
-
- public override string Name => "bizsocket";
-
- [LuaMethodExample("local ws = bizsocket.open('wss://echo.websocket.org');")]
- [LuaMethod("open", "Opens a websocket and returns the id so that it can be retrieved later.")]
- public string OpenSocket(string url)
- {
- var ws = new ClientWebSocket();
- var id = new Guid().ToString();
-
- ws.ConnectAsync(new Uri(url), CancellationToken.None).Wait();
-
- activeSockets[id] = ws;
-
- return id;
- }
-
- [LuaMethodExample("local ws = bizsocket.send(ws_id, 'some message', true);")]
- [LuaMethod("send", "Send a message to a certain websocket id (boolean flag endOfMessage)")]
- public void SendToSocket(string id, string content, bool endOfMessage)
- {
- var ws = activeSockets[id];
- var msg = new ArraySegment(Encoding.UTF8.GetBytes(content));
-
- ws.SendAsync(msg, WebSocketMessageType.Text, endOfMessage, CancellationToken.None);
- }
-
- [LuaMethodExample("local ws = bizsocket.receive(ws_id, max_read);")]
- [LuaMethod("receive", "Receive a message from a certain websocket id and a maximum number of bytes to read.")]
- public string ReceiveFromSocket(string id, int maxRead)
- {
- var ws = activeSockets[id];
-
- var rcvBytes = new byte[maxRead];
- var rcvBuffer = new ArraySegment(rcvBytes);
-
- var result = ws.ReceiveAsync(rcvBuffer, CancellationToken.None).Result;
- string rcvMsg = Encoding.UTF8.GetString(rcvBuffer.Take(result.Count).ToArray());
-
- return rcvMsg;
- }
-
- [LuaMethodExample("local ws_status = bizsocket.getstatus(ws_id);")]
- [LuaMethod("getStatus", "Get a websocket's status")]
- public int GetSocketStatus(string id)
- {
- var ws = activeSockets[id];
-
- return (int)ws.State;
- }
-
- [LuaMethodExample("local ws_status = bizsocket.close(ws_id, close_status);")]
- [LuaMethod("close", "Close a websocket connection with a close status, defined in section 11.7 of the web sockets protocol spec.")]
- public void CloseSocket(string id, int status, string closeMessage)
- {
- var ws = activeSockets[id];
-
- ws.CloseAsync((WebSocketCloseStatus) status, closeMessage, CancellationToken.None);
- }
- }
-}
diff --git a/src/BizHawk.Client.EmuHawk/Api/Libraries/CommApi.cs b/src/BizHawk.Client.EmuHawk/Api/Libraries/CommApi.cs
index 2135bfa5dd..023261ce95 100644
--- a/src/BizHawk.Client.EmuHawk/Api/Libraries/CommApi.cs
+++ b/src/BizHawk.Client.EmuHawk/Api/Libraries/CommApi.cs
@@ -8,6 +8,8 @@ namespace BizHawk.Client.EmuHawk
{
public sealed class CommApi : ICommApi
{
+ private static readonly WebSocketServer _wsServer = new WebSocketServer();
+
private readonly (HttpCommunication HTTP, MemoryMappedFiles MMF, SocketServer Sockets) _networkingHelpers;
public HttpCommunication? HTTP => _networkingHelpers.HTTP;
@@ -16,6 +18,8 @@ namespace BizHawk.Client.EmuHawk
public SocketServer? Sockets => _networkingHelpers.Sockets;
+ public WebSocketServer? WebSockets => _wsServer;
+
public CommApi(Action logCallback, DisplayManager displayManager, InputManager inputManager, IMainFormForApi mainForm)
{
_networkingHelpers = mainForm.NetworkingHelpers;