Unclaimed Package Is this your package? Claim it to unlock full analytics and manage your listing.
Claim This Package

Install via UPM

Add to Unity Package Manager using this URL

https://www.pkglnk.dev/mikerochip-websocket.git

README Markdown

Copy this to your project's README.md

Style
Preview
pkglnk installs badge
## Installation

Add **WebSocket Client** to your Unity project via Package Manager:

1. Open **Window > Package Manager**
2. Click **+** > **Add package from git URL**
3. Enter:
```
https://www.pkglnk.dev/mikerochip-websocket.git
```

[![pkglnk](https://www.pkglnk.dev/badge/mikerochip-websocket.svg?style=pkglnk)](https://www.pkglnk.dev/pkg/mikerochip-websocket)

README

WebSocketConnection

Unity Version

WebSocketConnection is an ergonomic, idiomatic websocket client for Unity. Write-once for all platforms, including Web.

  • Ergonomics
    • Does not force you to use async
    • Does not force you to use #if conditional compilation
    • Listen for messages however you want: events, coroutines, polling, or async/await
  • Reliability
    • Reusable: connect, disconnect, change URL, connect, ...
    • string is sent as text type, byte[] as binary type
    • Proper handling of disconnects and bad messages
    • Tested with 100+ WebSocketConnection components with minimal drop in framerate
  • Extended Features
    • Custom ping-pongs which can be configured to calculate ping times. â„šī¸ You must control the server for this.
    • Self-signed certificate support. âš ī¸ Non-Web only, must use .NET Framework Api level.

Why Use This?

WebSocketConnection has the most ergonomic, write-once support across platforms:

Supports WebSocketConnection NativeWebSocket System.Net.WebSockets.ClientWebSocket
WebGL ✅ ✅ â›”ī¸
WebAssembly.Table ✅ ✅ â›”ī¸
Requires using async ✅ Optional âš ī¸ Required âš ī¸ Required
Code changes for WebGL ✅ None ✅ None â›”ī¸ Need custom jslib
Self-signed certs ✅ non-Web â›”ī¸ ✅ non-Web
Write-once ping-pong ✅ â›”ī¸ â›”ī¸

Installation

See official instructions for how to Install a Package from a Git URL. The URL is

https://github.com/mikerochip/unity-websocket.git

Known Limitations

  • Self-Signed Certs
    • Only works in editor and non-web builds
    • Required Api Compatibility Level .NET Framework
    • Requires lots of fiddly setup, see sample below
  • Web Limitations
    • The underlying Web implementation uses the default browser JavaScript WebSocket API to minimize depedencies, which has limitations
    • No custom header support. See this
    • No self-signed certs support
    • No spec-compliant ping-pong support. â„šī¸ Use the custom ping-pong feature instead, which also adds ping timing support.

Test Projects

For reference, if you don't want to roll your own:

Samples

Assume we have a class like this for the following samples:

using MikeSchweitzer.WebSocket;

public class Tester : MonoBehaviour
{
    public WebSocketConnection _Connection;
    public string _Url = "wss://ws.postman-echo.com/raw";
}

Connect

// inline style
public void Connect()
{
    _Connection.Connect(_Url);
}

// property style
public void Connect()
{
    _Connection.DesiredConfig = new WebSocketConfig
    {
        Url = _Url,
    };
    _Connection.Connect();
}

Disconnect

public void Disconnect()
{
    _Connection.Disconnect();
}

State Querying

Update Style

private WebSocketState _oldState;

private void Update()
{
    var newState = WebSocketConnection.State;
    if (_oldState != newState)
    {
        Debug.Log($"OnStateChanged oldState={_oldState}|newState={newState}");
        _oldState = newState;
    }
}

Event Style

private void Awake()
{
    _Connection.StateChanged += OnStateChanged
}

private void OnDestroy()
{
    _Connection.StateChanged -= OnStateChanged;
}

private void OnStateChanged(WebSocketConnection connection, WebSocketState oldState, WebSocketState newState)
{
    Debug.Log($"OnStateChanged oldState={oldState}|newState={newState}");
}

Reconnect

Coroutine Style

public IEnumerator Reconnect()
{
    Disconnect();
    yield return new WaitUntil(_Connection.State == WebSocketState.Disconnected);

    // you may change the url here, if you want
    Connect();
}

Event Style

private void OnStateChanged(WebSocketConnection connection, WebSocketState oldState, WebSocketState newState)
{
    if (newState == WebSocketState.Disconnected)
    {
        // you may change the url here, if you want
        _Connection.Connect();
    }
}

Error Messages

[!NOTE] These are just error messages, not states. See the State Querying section.

Error messages are generally derived from platform-specific WebSocket errors.

private void Awake()
{
    _Connection.ErrorMessageReceived += OnErrorMessageReceived;
}

private void OnDestroy()
{
    _Connection.ErrorMessageReceived -= OnErrorMessageReceived;
}

private void OnErrorMessageReceived(WebSocketConnection connection, string errorMessage)
{
    // you can also use _Connection.ErrorMessage
    Debug.LogError(errorMessage);
}

Send Messages

[!WARNING] You must be Connected to send messages, otherwise you will get an error

public void SendString()
{
    _Connection.AddOutgoingMessage("hello");
}

public void SendBinary()
{
    var bytes = Encoding.UTF8.GetBytes("hello");
    _Connection.AddOutgoingMessage(bytes);
}

Receive Messages

Update Style

private void Update()
{
    while (_Connection.TryRemoveIncomingMessage(out string message))
        Debug.Log(message);
}

Event Style

private void Awake()
{
    _Connection.MessageReceived += OnMessageReceived;
}

private void OnDestroy()
{
    _Connection.MessageReceived -= OnMessageReceived;
}

private void OnMessageReceived(WebSocketConnection connection, WebSocketMessage message)
{
    Debug.Log(message.String);
}

Coroutine Style

private void Awake()
{
    StartCoroutine(ReceiveMessages());
}

private IEnumerator ReceiveMessages()
{
    while (true)
    {
        if (_Connection.TryRemoveIncomingMessage(out string message))
            Debug.Log(message);
        yield return null;
    }
}

Async/Await Style

private CancellationTokenSource _cts;

private async void Awake()
{
    _cts = new CancellationTokenSource();
    await ReceiveMessagesAsync();
}

private void OnDestroy()
{
    _cts.Cancel();
}

private async Task ReceiveMessagesAsync()
{
    while (!_cts.IsCancellationRequested)
    {
        if (_Connection.TryRemoveIncomingMessage(out string message))
            Debug.Log(message);

        await Task.Yield();
    }
}

Custom Ping-Pong Support

This package has a custom ping-pong feature that you can write once for Web and non-Web builds.

[!WARNING]

  • Your server must be configured to echo messages of the same message type (text or binary) and content.
  • This package has custom ping-pong support because the default browser JavaScript WebSocket client does not implement the WebSocket Ping Pong spec even though .NET's WebSocketClient does implement the spec.

Enable Text Ping-Pongs

private void ConfigureStringPings()
{
    _Connection.DesiredConfig = new WebSocketConfig
    {
        Url = _Url,
        PingInterval = TimeSpan.FromSeconds(30),
        PingMessage = new WebSocketMessage("hi"),
    };
}

Enable Binary Ping-Pongs

private byte[] _pingBytes = Encoding.UTF8.GetBytes("hi");
private void ConfigureBinaryPings()
{
    _Connection.DesiredConfig = new WebSocketConfig
    {
        Url = _Url,
        PingInterval = TimeSpan.FromSeconds(30),
        PingMessage = new WebSocketMessage(_pingBytes),
    };
}

Ping Timing (Round-Trip-Time Tracking)

private void Awake()
{
    _Connection.DesiredConfig = new WebSocketConfig
    {
        Url = _Url,
        PingInterval = TimeSpan.FromSeconds(3),
        PingMessage = new WebSocketMessage("hi"),
        ShouldPingWaitForPong = true,
    };
    _Connection.PingSent += OnPingSent;
    _Connection.PongReceived += OnPongReceived;
}

private void OnDestroy()
{
    _Connection.PingSent -= OnPingSent;
    _Connection.PongReceived -= OnPongReceived;
}

private void OnPingSent(WebSocketConnection connection, DateTime timestamp)
{
    Debug.Log($"OnPingSent timestamp={timestamp:HH:mm:ss.ffff}");
}

private void OnPongReceived(WebSocketConnection connection, DateTime timestamp)
{
    Debug.Log($"OnPongReceived timestamp={timestamp:HH:mm:ss.ffff}");
    Debug.Log($"OnPongReceived RTT={connection.LastPingPongInterval:ss\\.ffff}");
}

Self-Signed Certificates

If you must use self-signed certificates, then there is a way to make that work with this package by following roughly these steps.

[!WARNING] I highly recommend against self-signed certs. These steps are easy to mess up and overly complicated.

I highly recommend instead:

  • Trusted CA certs
  • CA certs pre-installed on your servers and devices
  • Just using insecure ws:
  1. Create a certificate with e.g. openssl
    • Example: openssl req -x509 -newkey rsa:2048 -nodes -out cert.pem -keyout key.pem -days 365 -subj "/CN=example.local" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1,DNS:my-example-domain.com"
    • In the above example, replace my-example-domain.com with your domain name (if you have one, otherwise leave out the DNS: SAN)
  2. Export a pfx file from your cert
    • Example: openssl pkcs12 -export -in cert.pem -inkey key.pem -out cert.pfx -password pass:mypass -macalg SHA1 -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES
    • In the above example, replace mypass with a password of your choosing
    • âš ī¸NOTE: You MUST use these algorithm options or Unity will fail to load your cert
  3. Set your Unity project's Api Compatibility Level to .NET Framework
  4. Create a class like this somewhere in your project
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    
    private class SelfSignedCertTrustPolicy : ICertificatePolicy
    {
        public bool CheckValidationResult(ServicePoint servicePoint, X509Certificate certificate, WebRequest request, int certificateProblem)
        {
            return true;
        }
    }
    
  5. In some Awake() method somewhere, add this line:
    ServicePointManager.CertificatePolicy = new SelfSignedCertTrustPolicy();
    
  6. Configure your server to load the certs
    • This totally depends on your server
    • You can see an example from my test server here
  7. Configure this Unity package to load the certs
    • Put your cert.pfx in your Unity project as cert.pfx.bytes
    • Do similar with the password you made earlier: put your password in a text file like cert.pfx.pass.bytes
    • Load both of those in your code as TextAsset
    • Then do MyWebSocketConfig.DotNetSelfSignedCert = MyCert.bytes
    • And MyWebSocketConfig.DotNetSelfSignedCertPassword = MyCertPassword.text.ToCharArray()

WebSocketConnection should now be able to use wss: to connect to your server.

Attribution

Based on this repo by Endel Dreyer, which was based on this repo by Jiri Hybek

See license and third party notices for full attribution.

Comments

No comments yet. Be the first!