Skip to content

Commit

Permalink
com.utilities.websockets 1.0.0 (#3)
Browse files Browse the repository at this point in the history
- Initial Release! πŸŽ‰πŸš€
  • Loading branch information
StephenHodgson authored Oct 13, 2024
1 parent 8b28a0a commit c5db730
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 23 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ socket.OnClose += (code, reason) => Debug.Log($"Connection Closed: {code} {reaso
socket.Connect();
```

> [!NOTE]
> `socket.ConnectAsync()` is blocking until the connection is closed.
### Handling Events

You can subscribe to the `OnOpen`, `OnMessage`, `OnError`, and `OnClose` events to handle respective situations:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ socket.OnClose += (code, reason) => Debug.Log($"Connection Closed: {code} {reaso
socket.Connect();
```

> [!NOTE]
> `socket.ConnectAsync()` is blocking until the connection is closed.
### Handling Events

You can subscribe to the `OnOpen`, `OnMessage`, `OnError`, and `OnClose` events to handle respective situations:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public interface IWebSocket : IDisposable
/// </summary>
Uri Address { get; }

/// <summary>
/// The request headers used by the <see cref="IWebSocket"/>.
/// </summary>
IReadOnlyDictionary<string, string> RequestHeaders { get; }

/// <summary>
/// The sub-protocols used by the <see cref="IWebSocket"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var UnityWebSocketLibrary = {
/**
* Create a new WebSocket instance and adds it to the $webSockets array.
* @param {string} url - The URL to which to connect.
* @param {string[]} subProtocols - An array of strings that indicate the sub-protocols the client is willing to speak.
* @param {string[]} subProtocols - An json array of strings that indicate the sub-protocols the client is willing to speak.
* @returns {number} - A pointer to the WebSocket instance.
* @param {function} onOpenCallback - The callback function. WebSocket_OnOpenDelegate(IntPtr websocketPtr) in C#.
* @param {function} onMessageCallback - The callback function. WebSocket_OnMessageDelegate(IntPtr websocketPtr, IntPtr data, int length, int type) in C#.
Expand All @@ -22,7 +22,7 @@ var UnityWebSocketLibrary = {

try {
var subProtocolsStr = UTF8ToString(subProtocols);
var subProtocolsArr = subProtocolsStr ? subProtocolsStr.split(',') : undefined;
var subProtocolsArr = subProtocolsStr ? JSON.parse(subProtocolsStr) : undefined;

for (var i = 0; i < webSockets.length; i++) {
var instance = webSockets[i];
Expand All @@ -43,11 +43,13 @@ var UnityWebSocketLibrary = {
onCloseCallback: onCloseCallback
};

if (subProtocolsArr) {
if (subProtocolsArr && Array.isArray(subProtocolsArr)) {
webSockets[socketPtr].subProtocols = subProtocolsArr;
} else {
console.error('subProtocols is not an array');
}

// console.log('Created WebSocket object with websocketPtr: ', socketPtr, ' for URL: ', urlStr, ' and sub-protocols: ', subProtocolsArr)
// console.log(`Created WebSocket object with websocketPtr: ${socketPtr} for URL: ${urlStr}, sub-protocols: ${subProtocolsArr}`);
return socketPtr;
} catch (error) {
console.error('Error creating WebSocket object for URL: ', urlStr, ' Error: ', error);
Expand Down Expand Up @@ -81,6 +83,11 @@ var UnityWebSocketLibrary = {
try {
var instance = webSockets[socketPtr];

if (!instance) {
console.error('WebSocket instance not found for websocketPtr: ', socketPtr);
return;
}

if (!instance.subProtocols || instance.subProtocols.length === 0) {
instance.socket = new WebSocket(instance.url);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ namespace Utilities.WebSockets
{
public class WebSocket : IWebSocket
{
public WebSocket(string url, IReadOnlyList<string> subProtocols = null)
: this(new Uri(url), subProtocols)
public WebSocket(string url, IReadOnlyDictionary<string, string> requestHeaders = null, IReadOnlyList<string> subProtocols = null)
: this(new Uri(url), requestHeaders, subProtocols)
{
}

public WebSocket(Uri uri, IReadOnlyList<string> subProtocols = null)
public WebSocket(Uri uri, IReadOnlyDictionary<string, string> requestHeaders = null, IReadOnlyList<string> subProtocols = null)
{
var protocol = uri.Scheme;

Expand All @@ -32,6 +32,7 @@ public WebSocket(Uri uri, IReadOnlyList<string> subProtocols = null)
}

Address = uri;
RequestHeaders = requestHeaders ?? new Dictionary<string, string>();
SubProtocols = subProtocols ?? new List<string>();
_socket = new ClientWebSocket();
RunMessageQueue();
Expand Down Expand Up @@ -59,10 +60,7 @@ private async void RunMessageQueue()
}
}

~WebSocket()
{
Dispose(false);
}
~WebSocket() => Dispose(false);

#region IDisposable

Expand Down Expand Up @@ -114,6 +112,9 @@ public void Dispose()
/// <inheritdoc />
public Uri Address { get; }

/// <inheritdoc />
public IReadOnlyDictionary<string, string> RequestHeaders { get; }

/// <inheritdoc />
public IReadOnlyList<string> SubProtocols { get; }

Expand All @@ -126,7 +127,7 @@ public void Dispose()
_ => State.Closed
};

private object _lock = new();
private readonly object _lock = new();
private ClientWebSocket _socket;
private SemaphoreSlim _semaphore = new(1, 1);
private CancellationTokenSource _lifetimeCts;
Expand All @@ -151,13 +152,19 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
_lifetimeCts?.Dispose();
_lifetimeCts = new CancellationTokenSource();
using var cts = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeCts.Token, cancellationToken);
cancellationToken = cts.Token;

foreach (var requestHeader in RequestHeaders)
{
_socket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
}

foreach (var subProtocol in SubProtocols)
{
_socket.Options.AddSubProtocol(subProtocol);
}

await _socket.ConnectAsync(Address, cts.Token).ConfigureAwait(false);
await _socket.ConnectAsync(Address, cancellationToken).ConfigureAwait(false);
_events.Enqueue(() => OnOpen?.Invoke());
var buffer = new Memory<byte>(new byte[8192]);

Expand All @@ -168,11 +175,12 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)

do
{
result = await _socket.ReceiveAsync(buffer, cts.Token).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
result = await _socket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
stream.Write(buffer.Span[..result.Count]);
} while (!result.EndOfMessage);

await stream.FlushAsync(cts.Token).ConfigureAwait(false);
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
var memory = new ReadOnlyMemory<byte>(stream.GetBuffer(), 0, (int)stream.Length);

if (result.MessageType != WebSocketMessageType.Close)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@
using System.Threading.Tasks;
using UnityEngine;
using Utilities.Async;
using Newtonsoft.Json;

namespace Utilities.WebSockets
{
public class WebSocket : IWebSocket
{
public WebSocket(string url, IReadOnlyList<string> subProtocols = null)
: this(new Uri(url), subProtocols)
public WebSocket(string url, IReadOnlyDictionary<string, string> requestHeaders = null, IReadOnlyList<string> subProtocols = null)
: this(new Uri(url), requestHeaders, subProtocols)
{
}

public WebSocket(Uri uri, IReadOnlyList<string> subProtocols = null)
public WebSocket(Uri uri, IReadOnlyDictionary<string, string> requestHeaders = null, IReadOnlyList<string> subProtocols = null)
{
var protocol = uri.Scheme;

Expand All @@ -30,9 +31,21 @@ public WebSocket(Uri uri, IReadOnlyList<string> subProtocols = null)
throw new ArgumentException($"Unsupported protocol: {protocol}");
}

if (requestHeaders is { Count: > 0 })
{
Debug.LogWarning("Request Headers are not supported in WebGL and will be ignored.");
}

Address = uri;
SubProtocols = subProtocols ?? new List<string>();
_socket = WebSocket_Create(uri.ToString(), string.Join(',', SubProtocols), WebSocket_OnOpen, WebSocket_OnMessage, WebSocket_OnError, WebSocket_OnClose);
RequestHeaders = requestHeaders ?? new Dictionary<string, string>();
_socket = WebSocket_Create(
uri.ToString(),
JsonConvert.SerializeObject(subProtocols),
WebSocket_OnOpen,
WebSocket_OnMessage,
WebSocket_OnError,
WebSocket_OnClose);

if (_socket == IntPtr.Zero || !_sockets.TryAdd(_socket, this))
{
Expand Down Expand Up @@ -210,6 +223,8 @@ private static void WebSocket_OnClose(IntPtr websocketPtr, CloseStatusCode code,
/// <inheritdoc />
public Uri Address { get; }

public IReadOnlyDictionary<string, string> RequestHeaders { get; }

/// <inheritdoc />
public IReadOnlyList<string> SubProtocols { get; }

Expand All @@ -218,7 +233,7 @@ private static void WebSocket_OnClose(IntPtr websocketPtr, CloseStatusCode code,
? (State)WebSocket_GetState(_socket)
: State.Closed;

private object _lock = new();
private readonly object _lock = new();
private IntPtr _socket;
private SemaphoreSlim _semaphore = new(1, 1);
private CancellationTokenSource _lifetimeCts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Utilities.WebSockets",
"description": "A simple websocket package for Unity (UPM)",
"keywords": [],
"version": "1.0.0-preview.2",
"version": "1.0.0",
"unity": "2021.3",
"documentationUrl": "https://github.com/RageAgainstThePixel/com.utilities.websockets#documentation",
"changelogUrl": "https://github.com/RageAgainstThePixel/com.utilities.websockets/releases",
Expand All @@ -17,7 +17,8 @@
"url": "https://github.com/StephenHodgson"
},
"dependencies": {
"com.utilities.async": "2.1.7"
"com.utilities.async": "2.1.7",
"com.unity.nuget.newtonsoft-json": "3.2.1"
},
"samples": [
{
Expand Down
2 changes: 1 addition & 1 deletion Utilities.Websockets/Packages/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"com.unity.ide.rider": "3.0.31",
"com.unity.ide.visualstudio": "2.0.22",
"com.unity.test-framework": "1.1.33",
"com.utilities.buildpipeline": "1.4.1",
"com.utilities.buildpipeline": "1.5.0",
"com.unity.modules.uielements": "1.0.0"
},
"scopedRegistries": [
Expand Down

0 comments on commit c5db730

Please sign in to comment.