├── .gitignore ├── LICENSE ├── Scripts └── WebSocketDemo.cs ├── README.md └── Plugins ├── WebSocket.jslib └── WebSocket.cs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Jiri Hybek 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /Scripts/WebSocketDemo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using UnityEngine; 5 | 6 | // Use plugin namespace 7 | using HybridWebSocket; 8 | 9 | public class WebSocketDemo : MonoBehaviour { 10 | 11 | // Use this for initialization 12 | void Start () { 13 | 14 | // Create WebSocket instance 15 | WebSocket ws = WebSocketFactory.CreateInstance("ws://echo.websocket.org"); 16 | 17 | // Add OnOpen event listener 18 | ws.OnOpen += () => 19 | { 20 | Debug.Log("WS connected!"); 21 | Debug.Log("WS state: " + ws.GetState().ToString()); 22 | 23 | ws.Send(Encoding.UTF8.GetBytes("Hello from Unity 3D!")); 24 | }; 25 | 26 | // Add OnMessage event listener 27 | ws.OnMessage += (byte[] msg) => 28 | { 29 | Debug.Log("WS received message: " + Encoding.UTF8.GetString(msg)); 30 | 31 | ws.Close(); 32 | }; 33 | 34 | // Add OnError event listener 35 | ws.OnError += (string errMsg) => 36 | { 37 | Debug.Log("WS error: " + errMsg); 38 | }; 39 | 40 | // Add OnClose event listener 41 | ws.OnClose += (WebSocketCloseCode code) => 42 | { 43 | Debug.Log("WS closed with code: " + code.ToString()); 44 | }; 45 | 46 | // Connect to the server 47 | ws.Connect(); 48 | 49 | } 50 | 51 | // Update is called once per frame 52 | void Update () { 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unity-websocket-webgl 2 | 3 | ## Maintainers Wanted 4 | 5 | I've created this library as a Hobby project when I was experimenting with Unity. I haven't used Unity for over 2 years and I'm not able to maintain the repo. If you want to become a maintainer a keep this project running, send me an e-mail or create an issue. Thank you for understanding. 6 | 7 | --- 8 | 9 | Hybrid event-driven WebSocket implementation for Unity 3D. 10 | 11 | It automatically compiles browser or native implementation based on project's target platform. Native implementation is using [WebSocketSharp](https://github.com/sta/websocket-sharp) library (must be downloaded separately - see below). For the browser implementation the custom emscripten JSLIB is used. 12 | 13 | **Warning:** WebSocket client is intended to support only binary messages. So if you want to send or receive string messages you must convert it to/from byte array in your code. 14 | 15 | ## Downloading WebSocketSharp 16 | 17 | You can get `WebSocketSharp.dll` from NuGet package manager. See [NuGet Gallery: websocket-sharp](http://www.nuget.org/packages/WebSocketSharp). 18 | 19 | Library can be installed manually. Just download the NuGet package. Then rename the file extension from `.nupkg` to `.zip` and extract it. Now you can copy `lib/websocket-sharp.dll` file to your Unity project into the `Assets/Plugins` directory. 20 | 21 | For more info please visit official [WebSocketSharp GitHub repo](https://github.com/sta/websocket-sharp). 22 | 23 | ## Installing plugin 24 | 25 | To install this plugin just copy the contents of `Plugins` directory to your Unity project's `Assets/Plugins` directory. 26 | 27 | ## Usage 28 | 29 | For example usage see `Scripts/WebSocketDemo.cs` file. You can import it to your project and assign it to any GameObject. Then run your project and check the console. 30 | 31 | ```csharp 32 | using System.Collections; 33 | using System.Collections.Generic; 34 | using System.Text; 35 | using UnityEngine; 36 | 37 | // Use plugin namespace 38 | using HybridWebSocket; 39 | 40 | public class WebSocketDemo : MonoBehaviour { 41 | 42 | // Use this for initialization 43 | void Start () { 44 | 45 | // Create WebSocket instance 46 | WebSocket ws = WebSocketFactory.CreateInstance("ws://echo.websocket.org"); 47 | 48 | // Add OnOpen event listener 49 | ws.OnOpen += () => 50 | { 51 | Debug.Log("WS connected!"); 52 | Debug.Log("WS state: " + ws.GetState().ToString()); 53 | 54 | ws.Send(Encoding.UTF8.GetBytes("Hello from Unity 3D!")); 55 | }; 56 | 57 | // Add OnMessage event listener 58 | ws.OnMessage += (byte[] msg) => 59 | { 60 | Debug.Log("WS received message: " + Encoding.UTF8.GetString(msg)); 61 | 62 | ws.Close(); 63 | }; 64 | 65 | // Add OnError event listener 66 | ws.OnError += (string errMsg) => 67 | { 68 | Debug.Log("WS error: " + errMsg); 69 | }; 70 | 71 | // Add OnClose event listener 72 | ws.OnClose += (WebSocketCloseCode code) => 73 | { 74 | Debug.Log("WS closed with code: " + code.ToString()); 75 | }; 76 | 77 | // Connect to the server 78 | ws.Connect(); 79 | 80 | } 81 | 82 | // Update is called once per frame 83 | void Update () { 84 | 85 | } 86 | } 87 | ``` 88 | 89 | ## Error Handling 90 | 91 | When any error occours during method call then the `WebSocketException` is thrown. Unified both for native and browser client so you can catch it in your C# code. 92 | 93 | ## License Apache 2.0 94 | 95 | Copyright 2018 Jiri Hybek 96 | 97 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 98 | 99 | ``` 100 | http://www.apache.org/licenses/LICENSE-2.0 101 | ``` 102 | 103 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 104 | -------------------------------------------------------------------------------- /Plugins/WebSocket.jslib: -------------------------------------------------------------------------------- 1 | /* 2 | * unity-websocket-webgl 3 | * 4 | * @author Jiri Hybek 5 | * @copyright 2018 Jiri Hybek 6 | * @license Apache 2.0 - See LICENSE file distributed with this source code. 7 | */ 8 | 9 | var LibraryWebSocket = { 10 | $webSocketState: { 11 | /* 12 | * Map of instances 13 | * 14 | * Instance structure: 15 | * { 16 | * url: string, 17 | * ws: WebSocket 18 | * } 19 | */ 20 | instances: {}, 21 | 22 | /* Last instance ID */ 23 | lastId: 0, 24 | 25 | /* Event listeners */ 26 | onOpen: null, 27 | onMesssage: null, 28 | onError: null, 29 | onClose: null, 30 | 31 | /* Debug mode */ 32 | debug: false 33 | }, 34 | 35 | /** 36 | * Set onOpen callback 37 | * 38 | * @param callback Reference to C# static function 39 | */ 40 | WebSocketSetOnOpen: function(callback) { 41 | 42 | webSocketState.onOpen = callback; 43 | 44 | }, 45 | 46 | /** 47 | * Set onMessage callback 48 | * 49 | * @param callback Reference to C# static function 50 | */ 51 | WebSocketSetOnMessage: function(callback) { 52 | 53 | webSocketState.onMessage = callback; 54 | 55 | }, 56 | 57 | /** 58 | * Set onError callback 59 | * 60 | * @param callback Reference to C# static function 61 | */ 62 | WebSocketSetOnError: function(callback) { 63 | 64 | webSocketState.onError = callback; 65 | 66 | }, 67 | 68 | /** 69 | * Set onClose callback 70 | * 71 | * @param callback Reference to C# static function 72 | */ 73 | WebSocketSetOnClose: function(callback) { 74 | 75 | webSocketState.onClose = callback; 76 | 77 | }, 78 | 79 | /** 80 | * Allocate new WebSocket instance struct 81 | * 82 | * @param url Server URL 83 | */ 84 | WebSocketAllocate: function(url) { 85 | 86 | var urlStr = UTF8ToString(url); 87 | var id = webSocketState.lastId++; 88 | 89 | webSocketState.instances[id] = { 90 | url: urlStr, 91 | ws: null 92 | }; 93 | 94 | return id; 95 | 96 | }, 97 | 98 | /** 99 | * Remove reference to WebSocket instance 100 | * 101 | * If socket is not closed function will close it but onClose event will not be emitted because 102 | * this function should be invoked by C# WebSocket destructor. 103 | * 104 | * @param instanceId Instance ID 105 | */ 106 | WebSocketFree: function(instanceId) { 107 | 108 | var instance = webSocketState.instances[instanceId]; 109 | 110 | if (!instance) return 0; 111 | 112 | // Close if not closed 113 | if (instance.ws !== null && instance.ws.readyState < 2) 114 | instance.ws.close(); 115 | 116 | // Remove reference 117 | delete webSocketState.instances[instanceId]; 118 | 119 | return 0; 120 | 121 | }, 122 | 123 | /** 124 | * Connect WebSocket to the server 125 | * 126 | * @param instanceId Instance ID 127 | */ 128 | WebSocketConnect: function(instanceId) { 129 | 130 | var instance = webSocketState.instances[instanceId]; 131 | if (!instance) return -1; 132 | 133 | if (instance.ws !== null) 134 | return -2; 135 | 136 | instance.ws = new WebSocket(instance.url); 137 | 138 | instance.ws.binaryType = 'arraybuffer'; 139 | 140 | instance.ws.onopen = function() { 141 | 142 | if (webSocketState.debug) 143 | console.log("[JSLIB WebSocket] Connected."); 144 | 145 | if (webSocketState.onOpen) 146 | Module['dynCall_vi'](webSocketState.onOpen, [ instanceId ]); 147 | 148 | }; 149 | 150 | instance.ws.onmessage = function(ev) { 151 | 152 | if (webSocketState.debug) 153 | console.log("[JSLIB WebSocket] Received message:", ev.data); 154 | 155 | if (webSocketState.onMessage === null) 156 | return; 157 | 158 | if (ev.data instanceof ArrayBuffer) { 159 | 160 | var dataBuffer = new Uint8Array(ev.data); 161 | 162 | var buffer = _malloc(dataBuffer.length); 163 | HEAPU8.set(dataBuffer, buffer); 164 | 165 | try { 166 | Module['dynCall_viii']( webSocketState.onMessage, [ instanceId, buffer, dataBuffer.length ]); 167 | } finally { 168 | _free(buffer); 169 | } 170 | 171 | } else if (typeof ev.data == 'string') { 172 | var arrBuffer = new ArrayBuffer(ev.data.length) 173 | var dataBuffer = new Uint8Array(arrBuffer) 174 | for (var i = 0, len = ev.data.length; i < len; i++) { 175 | dataBuffer[i] = ev.data.charCodeAt(i) 176 | } 177 | var buffer = _malloc(dataBuffer.length); 178 | HEAPU8.set(dataBuffer, buffer); 179 | try { 180 | Runtime.dynCall("viii", webSocketState.onMessage, [ instanceId, buffer, dataBuffer.length ]); 181 | } finally { 182 | _free(buffer); 183 | } 184 | } 185 | 186 | }; 187 | 188 | instance.ws.onerror = function(ev) { 189 | 190 | if (webSocketState.debug) 191 | console.log("[JSLIB WebSocket] Error occured."); 192 | 193 | if (webSocketState.onError) { 194 | 195 | var msg = "WebSocket error."; 196 | var msgBytes = lengthBytesUTF8(msg); 197 | var msgBuffer = _malloc(msgBytes + 1); 198 | stringToUTF8(msg, msgBuffer, msgBytes); 199 | 200 | try { 201 | Module['dynCall_vii']( webSocketState.onError, [ instanceId, msgBuffer ]); 202 | } finally { 203 | _free(msgBuffer); 204 | } 205 | 206 | } 207 | 208 | }; 209 | 210 | instance.ws.onclose = function(ev) { 211 | 212 | if (webSocketState.debug) 213 | console.log("[JSLIB WebSocket] Closed."); 214 | 215 | if (webSocketState.onClose) 216 | Module['dynCall_vii']( webSocketState.onClose, [ instanceId, ev.code ]); 217 | 218 | delete instance.ws; 219 | 220 | }; 221 | 222 | return 0; 223 | 224 | }, 225 | 226 | /** 227 | * Close WebSocket connection 228 | * 229 | * @param instanceId Instance ID 230 | * @param code Close status code 231 | * @param reasonPtr Pointer to reason string 232 | */ 233 | WebSocketClose: function(instanceId, code, reasonPtr) { 234 | 235 | var instance = webSocketState.instances[instanceId]; 236 | if (!instance) return -1; 237 | 238 | if (instance.ws === null) 239 | return -3; 240 | 241 | if (instance.ws.readyState === 2) 242 | return -4; 243 | 244 | if (instance.ws.readyState === 3) 245 | return -5; 246 | 247 | var reason = ( reasonPtr ? UTF8ToString(reasonPtr) : undefined ); 248 | 249 | try { 250 | instance.ws.close(code, reason); 251 | } catch(err) { 252 | return -7; 253 | } 254 | 255 | return 0; 256 | 257 | }, 258 | 259 | /** 260 | * Send message over WebSocket 261 | * 262 | * @param instanceId Instance ID 263 | * @param bufferPtr Pointer to the message buffer 264 | * @param length Length of the message in the buffer 265 | */ 266 | WebSocketSend: function(instanceId, bufferPtr, length) { 267 | 268 | var instance = webSocketState.instances[instanceId]; 269 | if (!instance) return -1; 270 | 271 | if (instance.ws === null) 272 | return -3; 273 | 274 | if (instance.ws.readyState !== 1) 275 | return -6; 276 | 277 | instance.ws.send(HEAPU8.buffer.slice(bufferPtr, bufferPtr + length)); 278 | 279 | return 0; 280 | 281 | }, 282 | 283 | /** 284 | * Return WebSocket readyState 285 | * 286 | * @param instanceId Instance ID 287 | */ 288 | WebSocketGetState: function(instanceId) { 289 | 290 | var instance = webSocketState.instances[instanceId]; 291 | if (!instance) return -1; 292 | 293 | if (instance.ws) 294 | return instance.ws.readyState; 295 | else 296 | return 3; 297 | 298 | } 299 | 300 | }; 301 | 302 | autoAddDeps(LibraryWebSocket, '$webSocketState'); 303 | mergeInto(LibraryManager.library, LibraryWebSocket); 304 | -------------------------------------------------------------------------------- /Plugins/WebSocket.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * unity-websocket-webgl 3 | * 4 | * @author Jiri Hybek 5 | * @copyright 2018 Jiri Hybek 6 | * @license Apache 2.0 - See LICENSE file distributed with this source code. 7 | */ 8 | 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Runtime.InteropServices; 12 | using UnityEngine; 13 | using AOT; 14 | 15 | namespace HybridWebSocket 16 | { 17 | 18 | /// 19 | /// Handler for WebSocket Open event. 20 | /// 21 | public delegate void WebSocketOpenEventHandler(); 22 | 23 | /// 24 | /// Handler for message received from WebSocket. 25 | /// 26 | public delegate void WebSocketMessageEventHandler(byte[] data); 27 | 28 | /// 29 | /// Handler for an error event received from WebSocket. 30 | /// 31 | public delegate void WebSocketErrorEventHandler(string errorMsg); 32 | 33 | /// 34 | /// Handler for WebSocket Close event. 35 | /// 36 | public delegate void WebSocketCloseEventHandler(WebSocketCloseCode closeCode); 37 | 38 | /// 39 | /// Enum representing WebSocket connection state 40 | /// 41 | public enum WebSocketState 42 | { 43 | Connecting, 44 | Open, 45 | Closing, 46 | Closed 47 | } 48 | 49 | /// 50 | /// Web socket close codes. 51 | /// 52 | public enum WebSocketCloseCode 53 | { 54 | /* Do NOT use NotSet - it's only purpose is to indicate that the close code cannot be parsed. */ 55 | NotSet = 0, 56 | Normal = 1000, 57 | Away = 1001, 58 | ProtocolError = 1002, 59 | UnsupportedData = 1003, 60 | Undefined = 1004, 61 | NoStatus = 1005, 62 | Abnormal = 1006, 63 | InvalidData = 1007, 64 | PolicyViolation = 1008, 65 | TooBig = 1009, 66 | MandatoryExtension = 1010, 67 | ServerError = 1011, 68 | TlsHandshakeFailure = 1015 69 | } 70 | 71 | /// 72 | /// WebSocket class interface shared by both native and JSLIB implementation. 73 | /// 74 | public interface IWebSocket 75 | { 76 | /// 77 | /// Open WebSocket connection 78 | /// 79 | void Connect(); 80 | 81 | /// 82 | /// Close WebSocket connection with optional status code and reason. 83 | /// 84 | /// Close status code. 85 | /// Reason string. 86 | void Close(WebSocketCloseCode code = WebSocketCloseCode.Normal, string reason = null); 87 | 88 | /// 89 | /// Send binary data over the socket. 90 | /// 91 | /// Payload data. 92 | void Send(byte[] data); 93 | 94 | /// 95 | /// Return WebSocket connection state. 96 | /// 97 | /// The state. 98 | WebSocketState GetState(); 99 | 100 | /// 101 | /// Occurs when the connection is opened. 102 | /// 103 | event WebSocketOpenEventHandler OnOpen; 104 | 105 | /// 106 | /// Occurs when a message is received. 107 | /// 108 | event WebSocketMessageEventHandler OnMessage; 109 | 110 | /// 111 | /// Occurs when an error was reported from WebSocket. 112 | /// 113 | event WebSocketErrorEventHandler OnError; 114 | 115 | /// 116 | /// Occurs when the socked was closed. 117 | /// 118 | event WebSocketCloseEventHandler OnClose; 119 | } 120 | 121 | /// 122 | /// Various helpers to work mainly with enums and exceptions. 123 | /// 124 | public static class WebSocketHelpers 125 | { 126 | 127 | /// 128 | /// Safely parse close code enum from int value. 129 | /// 130 | /// The close code enum. 131 | /// Close code as int. 132 | public static WebSocketCloseCode ParseCloseCodeEnum(int closeCode) 133 | { 134 | 135 | if (WebSocketCloseCode.IsDefined(typeof(WebSocketCloseCode), closeCode)) 136 | { 137 | return (WebSocketCloseCode)closeCode; 138 | } 139 | else 140 | { 141 | return WebSocketCloseCode.Undefined; 142 | } 143 | 144 | } 145 | 146 | /* 147 | * Return error message based on int code 148 | * 149 | 150 | */ 151 | /// 152 | /// Return an exception instance based on int code. 153 | /// 154 | /// Used for resolving JSLIB errors to meaninfull messages. 155 | /// 156 | /// Instance of an exception. 157 | /// Error code. 158 | /// Inner exception 159 | public static WebSocketException GetErrorMessageFromCode(int errorCode, Exception inner) 160 | { 161 | 162 | switch(errorCode) 163 | { 164 | 165 | case -1: return new WebSocketUnexpectedException("WebSocket instance not found.", inner); 166 | case -2: return new WebSocketInvalidStateException("WebSocket is already connected or in connecting state.", inner); 167 | case -3: return new WebSocketInvalidStateException("WebSocket is not connected.", inner); 168 | case -4: return new WebSocketInvalidStateException("WebSocket is already closing.", inner); 169 | case -5: return new WebSocketInvalidStateException("WebSocket is already closed.", inner); 170 | case -6: return new WebSocketInvalidStateException("WebSocket is not in open state.", inner); 171 | case -7: return new WebSocketInvalidArgumentException("Cannot close WebSocket. An invalid code was specified or reason is too long.", inner); 172 | default: return new WebSocketUnexpectedException("Unknown error.", inner); 173 | 174 | } 175 | 176 | } 177 | 178 | } 179 | 180 | /// 181 | /// Generic WebSocket exception class 182 | /// 183 | public class WebSocketException : Exception 184 | { 185 | 186 | public WebSocketException() 187 | { 188 | } 189 | 190 | public WebSocketException(string message) 191 | : base(message) 192 | { 193 | } 194 | 195 | public WebSocketException(string message, Exception inner) 196 | : base(message, inner) 197 | { 198 | } 199 | 200 | } 201 | 202 | /// 203 | /// Web socket exception raised when an error was not expected, probably due to corrupted internal state. 204 | /// 205 | public class WebSocketUnexpectedException : WebSocketException 206 | { 207 | public WebSocketUnexpectedException(){} 208 | public WebSocketUnexpectedException(string message) : base(message){} 209 | public WebSocketUnexpectedException(string message, Exception inner) : base(message, inner) {} 210 | } 211 | 212 | /// 213 | /// Invalid argument exception raised when bad arguments are passed to a method. 214 | /// 215 | public class WebSocketInvalidArgumentException : WebSocketException 216 | { 217 | public WebSocketInvalidArgumentException() { } 218 | public WebSocketInvalidArgumentException(string message) : base(message) { } 219 | public WebSocketInvalidArgumentException(string message, Exception inner) : base(message, inner) { } 220 | } 221 | 222 | /// 223 | /// Invalid state exception raised when trying to invoke action which cannot be done due to different then required state. 224 | /// 225 | public class WebSocketInvalidStateException : WebSocketException 226 | { 227 | public WebSocketInvalidStateException() { } 228 | public WebSocketInvalidStateException(string message) : base(message) { } 229 | public WebSocketInvalidStateException(string message, Exception inner) : base(message, inner) { } 230 | } 231 | 232 | #if UNITY_WEBGL && !UNITY_EDITOR 233 | /// 234 | /// WebSocket class bound to JSLIB. 235 | /// 236 | public class WebSocket: IWebSocket 237 | { 238 | 239 | /* WebSocket JSLIB functions */ 240 | [DllImport("__Internal")] 241 | public static extern int WebSocketConnect(int instanceId); 242 | 243 | [DllImport("__Internal")] 244 | public static extern int WebSocketClose(int instanceId, int code, string reason); 245 | 246 | [DllImport("__Internal")] 247 | public static extern int WebSocketSend(int instanceId, byte[] dataPtr, int dataLength); 248 | 249 | [DllImport("__Internal")] 250 | public static extern int WebSocketGetState(int instanceId); 251 | 252 | /// 253 | /// The instance identifier. 254 | /// 255 | protected int instanceId; 256 | 257 | /// 258 | /// Occurs when the connection is opened. 259 | /// 260 | public event WebSocketOpenEventHandler OnOpen; 261 | 262 | /// 263 | /// Occurs when a message is received. 264 | /// 265 | public event WebSocketMessageEventHandler OnMessage; 266 | 267 | /// 268 | /// Occurs when an error was reported from WebSocket. 269 | /// 270 | public event WebSocketErrorEventHandler OnError; 271 | 272 | /// 273 | /// Occurs when the socked was closed. 274 | /// 275 | public event WebSocketCloseEventHandler OnClose; 276 | 277 | /// 278 | /// Constructor - receive JSLIB instance id of allocated socket 279 | /// 280 | /// Instance identifier. 281 | public WebSocket(int instanceId) 282 | { 283 | 284 | this.instanceId = instanceId; 285 | 286 | } 287 | 288 | /// 289 | /// Destructor - notifies WebSocketFactory about it to remove JSLIB references 290 | /// Releases unmanaged resources and performs other cleanup operations before the 291 | /// is reclaimed by garbage collection. 292 | /// 293 | ~WebSocket() 294 | { 295 | WebSocketFactory.HandleInstanceDestroy(this.instanceId); 296 | } 297 | 298 | /// 299 | /// Return JSLIB instance ID 300 | /// 301 | /// The instance identifier. 302 | public int GetInstanceId() 303 | { 304 | 305 | return this.instanceId; 306 | 307 | } 308 | 309 | /// 310 | /// Open WebSocket connection 311 | /// 312 | public void Connect() 313 | { 314 | 315 | int ret = WebSocketConnect(this.instanceId); 316 | 317 | if (ret < 0) 318 | throw WebSocketHelpers.GetErrorMessageFromCode(ret, null); 319 | 320 | } 321 | 322 | /// 323 | /// Close WebSocket connection with optional status code and reason. 324 | /// 325 | /// Close status code. 326 | /// Reason string. 327 | public void Close(WebSocketCloseCode code = WebSocketCloseCode.Normal, string reason = null) 328 | { 329 | 330 | int ret = WebSocketClose(this.instanceId, (int)code, reason); 331 | 332 | if (ret < 0) 333 | throw WebSocketHelpers.GetErrorMessageFromCode(ret, null); 334 | 335 | } 336 | 337 | /// 338 | /// Send binary data over the socket. 339 | /// 340 | /// Payload data. 341 | public void Send(byte[] data) 342 | { 343 | 344 | int ret = WebSocketSend(this.instanceId, data, data.Length); 345 | 346 | if (ret < 0) 347 | throw WebSocketHelpers.GetErrorMessageFromCode(ret, null); 348 | 349 | } 350 | 351 | /// 352 | /// Return WebSocket connection state. 353 | /// 354 | /// The state. 355 | public WebSocketState GetState() 356 | { 357 | 358 | int state = WebSocketGetState(this.instanceId); 359 | 360 | if (state < 0) 361 | throw WebSocketHelpers.GetErrorMessageFromCode(state, null); 362 | 363 | switch (state) 364 | { 365 | case 0: 366 | return WebSocketState.Connecting; 367 | 368 | case 1: 369 | return WebSocketState.Open; 370 | 371 | case 2: 372 | return WebSocketState.Closing; 373 | 374 | case 3: 375 | return WebSocketState.Closed; 376 | 377 | default: 378 | return WebSocketState.Closed; 379 | } 380 | 381 | } 382 | 383 | /// 384 | /// Delegates onOpen event from JSLIB to native sharp event 385 | /// Is called by WebSocketFactory 386 | /// 387 | public void DelegateOnOpenEvent() 388 | { 389 | 390 | this.OnOpen?.Invoke(); 391 | 392 | } 393 | 394 | /// 395 | /// Delegates onMessage event from JSLIB to native sharp event 396 | /// Is called by WebSocketFactory 397 | /// 398 | /// Binary data. 399 | public void DelegateOnMessageEvent(byte[] data) 400 | { 401 | 402 | this.OnMessage?.Invoke(data); 403 | 404 | } 405 | 406 | /// 407 | /// Delegates onError event from JSLIB to native sharp event 408 | /// Is called by WebSocketFactory 409 | /// 410 | /// Error message. 411 | public void DelegateOnErrorEvent(string errorMsg) 412 | { 413 | 414 | this.OnError?.Invoke(errorMsg); 415 | 416 | } 417 | 418 | /// 419 | /// Delegate onClose event from JSLIB to native sharp event 420 | /// Is called by WebSocketFactory 421 | /// 422 | /// Close status code. 423 | public void DelegateOnCloseEvent(int closeCode) 424 | { 425 | 426 | this.OnClose?.Invoke(WebSocketHelpers.ParseCloseCodeEnum(closeCode)); 427 | 428 | } 429 | 430 | } 431 | #else 432 | public class WebSocket : IWebSocket 433 | { 434 | 435 | /// 436 | /// Occurs when the connection is opened. 437 | /// 438 | public event WebSocketOpenEventHandler OnOpen; 439 | 440 | /// 441 | /// Occurs when a message is received. 442 | /// 443 | public event WebSocketMessageEventHandler OnMessage; 444 | 445 | /// 446 | /// Occurs when an error was reported from WebSocket. 447 | /// 448 | public event WebSocketErrorEventHandler OnError; 449 | 450 | /// 451 | /// Occurs when the socked was closed. 452 | /// 453 | public event WebSocketCloseEventHandler OnClose; 454 | 455 | /// 456 | /// The WebSocketSharp instance. 457 | /// 458 | protected WebSocketSharp.WebSocket ws; 459 | 460 | /// 461 | /// WebSocket constructor. 462 | /// 463 | /// Valid WebSocket URL. 464 | public WebSocket(string url) 465 | { 466 | 467 | try 468 | { 469 | 470 | // Create WebSocket instance 471 | this.ws = new WebSocketSharp.WebSocket(url); 472 | 473 | // Bind OnOpen event 474 | this.ws.OnOpen += (sender, ev) => 475 | { 476 | this.OnOpen?.Invoke(); 477 | }; 478 | 479 | // Bind OnMessage event 480 | this.ws.OnMessage += (sender, ev) => 481 | { 482 | if (ev.RawData != null) 483 | this.OnMessage?.Invoke(ev.RawData); 484 | }; 485 | 486 | // Bind OnError event 487 | this.ws.OnError += (sender, ev) => 488 | { 489 | this.OnError?.Invoke(ev.Message); 490 | }; 491 | 492 | // Bind OnClose event 493 | this.ws.OnClose += (sender, ev) => 494 | { 495 | this.OnClose?.Invoke( 496 | WebSocketHelpers.ParseCloseCodeEnum( (int)ev.Code ) 497 | ); 498 | }; 499 | 500 | } 501 | catch (Exception e) 502 | { 503 | 504 | throw new WebSocketUnexpectedException("Failed to create WebSocket Client.", e); 505 | 506 | } 507 | 508 | } 509 | 510 | /// 511 | /// Open WebSocket connection 512 | /// 513 | public void Connect() 514 | { 515 | 516 | // Check state 517 | if (this.ws.ReadyState == WebSocketSharp.WebSocketState.Open || this.ws.ReadyState == WebSocketSharp.WebSocketState.Closing) 518 | throw new WebSocketInvalidStateException("WebSocket is already connected or is closing."); 519 | 520 | try 521 | { 522 | this.ws.ConnectAsync(); 523 | } 524 | catch (Exception e) 525 | { 526 | throw new WebSocketUnexpectedException("Failed to connect.", e); 527 | } 528 | 529 | } 530 | 531 | /// 532 | /// Close WebSocket connection with optional status code and reason. 533 | /// 534 | /// Close status code. 535 | /// Reason string. 536 | public void Close(WebSocketCloseCode code = WebSocketCloseCode.Normal, string reason = null) 537 | { 538 | 539 | // Check state 540 | if (this.ws.ReadyState == WebSocketSharp.WebSocketState.Closing) 541 | throw new WebSocketInvalidStateException("WebSocket is already closing."); 542 | 543 | if (this.ws.ReadyState == WebSocketSharp.WebSocketState.Closed) 544 | throw new WebSocketInvalidStateException("WebSocket is already closed."); 545 | 546 | try 547 | { 548 | this.ws.CloseAsync((ushort)code, reason); 549 | } 550 | catch (Exception e) 551 | { 552 | throw new WebSocketUnexpectedException("Failed to close the connection.", e); 553 | } 554 | 555 | } 556 | 557 | /// 558 | /// Send binary data over the socket. 559 | /// 560 | /// Payload data. 561 | public void Send(byte[] data) 562 | { 563 | 564 | // Check state 565 | if (this.ws.ReadyState != WebSocketSharp.WebSocketState.Open) 566 | throw new WebSocketInvalidStateException("WebSocket is not in open state."); 567 | 568 | try 569 | { 570 | this.ws.Send(data); 571 | } 572 | catch (Exception e) 573 | { 574 | throw new WebSocketUnexpectedException("Failed to send message.", e); 575 | } 576 | 577 | } 578 | 579 | /// 580 | /// Return WebSocket connection state. 581 | /// 582 | /// The state. 583 | public WebSocketState GetState() 584 | { 585 | 586 | switch (this.ws.ReadyState) 587 | { 588 | case WebSocketSharp.WebSocketState.Connecting: 589 | return WebSocketState.Connecting; 590 | 591 | case WebSocketSharp.WebSocketState.Open: 592 | return WebSocketState.Open; 593 | 594 | case WebSocketSharp.WebSocketState.Closing: 595 | return WebSocketState.Closing; 596 | 597 | case WebSocketSharp.WebSocketState.Closed: 598 | return WebSocketState.Closed; 599 | 600 | default: 601 | return WebSocketState.Closed; 602 | } 603 | 604 | } 605 | 606 | } 607 | #endif 608 | 609 | /// 610 | /// Class providing static access methods to work with JSLIB WebSocket or WebSocketSharp interface 611 | /// 612 | public static class WebSocketFactory 613 | { 614 | 615 | #if UNITY_WEBGL && !UNITY_EDITOR 616 | /* Map of websocket instances */ 617 | private static Dictionary instances = new Dictionary(); 618 | 619 | /* Delegates */ 620 | public delegate void OnOpenCallback(int instanceId); 621 | public delegate void OnMessageCallback(int instanceId, System.IntPtr msgPtr, int msgSize); 622 | public delegate void OnErrorCallback(int instanceId, System.IntPtr errorPtr); 623 | public delegate void OnCloseCallback(int instanceId, int closeCode); 624 | 625 | /* WebSocket JSLIB callback setters and other functions */ 626 | [DllImport("__Internal")] 627 | public static extern int WebSocketAllocate(string url); 628 | 629 | [DllImport("__Internal")] 630 | public static extern void WebSocketFree(int instanceId); 631 | 632 | [DllImport("__Internal")] 633 | public static extern void WebSocketSetOnOpen(OnOpenCallback callback); 634 | 635 | [DllImport("__Internal")] 636 | public static extern void WebSocketSetOnMessage(OnMessageCallback callback); 637 | 638 | [DllImport("__Internal")] 639 | public static extern void WebSocketSetOnError(OnErrorCallback callback); 640 | 641 | [DllImport("__Internal")] 642 | public static extern void WebSocketSetOnClose(OnCloseCallback callback); 643 | 644 | /* If callbacks was initialized and set */ 645 | private static bool isInitialized = false; 646 | 647 | /* 648 | * Initialize WebSocket callbacks to JSLIB 649 | */ 650 | private static void Initialize() 651 | { 652 | 653 | WebSocketSetOnOpen(DelegateOnOpenEvent); 654 | WebSocketSetOnMessage(DelegateOnMessageEvent); 655 | WebSocketSetOnError(DelegateOnErrorEvent); 656 | WebSocketSetOnClose(DelegateOnCloseEvent); 657 | 658 | isInitialized = true; 659 | 660 | } 661 | 662 | /// 663 | /// Called when instance is destroyed (by destructor) 664 | /// Method removes instance from map and free it in JSLIB implementation 665 | /// 666 | /// Instance identifier. 667 | public static void HandleInstanceDestroy(int instanceId) 668 | { 669 | 670 | instances.Remove(instanceId); 671 | WebSocketFree(instanceId); 672 | 673 | } 674 | 675 | [MonoPInvokeCallback(typeof(OnOpenCallback))] 676 | public static void DelegateOnOpenEvent(int instanceId) 677 | { 678 | 679 | WebSocket instanceRef; 680 | 681 | if (instances.TryGetValue(instanceId, out instanceRef)) 682 | { 683 | instanceRef.DelegateOnOpenEvent(); 684 | } 685 | 686 | } 687 | 688 | [MonoPInvokeCallback(typeof(OnMessageCallback))] 689 | public static void DelegateOnMessageEvent(int instanceId, System.IntPtr msgPtr, int msgSize) 690 | { 691 | 692 | WebSocket instanceRef; 693 | 694 | if (instances.TryGetValue(instanceId, out instanceRef)) 695 | { 696 | byte[] msg = new byte[msgSize]; 697 | Marshal.Copy(msgPtr, msg, 0, msgSize); 698 | 699 | instanceRef.DelegateOnMessageEvent(msg); 700 | } 701 | 702 | } 703 | 704 | [MonoPInvokeCallback(typeof(OnErrorCallback))] 705 | public static void DelegateOnErrorEvent(int instanceId, System.IntPtr errorPtr) 706 | { 707 | 708 | WebSocket instanceRef; 709 | 710 | if (instances.TryGetValue(instanceId, out instanceRef)) 711 | { 712 | 713 | string errorMsg = Marshal.PtrToStringAuto(errorPtr); 714 | instanceRef.DelegateOnErrorEvent(errorMsg); 715 | 716 | } 717 | 718 | } 719 | 720 | [MonoPInvokeCallback(typeof(OnCloseCallback))] 721 | public static void DelegateOnCloseEvent(int instanceId, int closeCode) 722 | { 723 | 724 | WebSocket instanceRef; 725 | 726 | if (instances.TryGetValue(instanceId, out instanceRef)) 727 | { 728 | instanceRef.DelegateOnCloseEvent(closeCode); 729 | } 730 | 731 | } 732 | #endif 733 | 734 | /// 735 | /// Create WebSocket client instance 736 | /// 737 | /// The WebSocket instance. 738 | /// WebSocket valid URL. 739 | public static WebSocket CreateInstance(string url) 740 | { 741 | #if UNITY_WEBGL && !UNITY_EDITOR 742 | if (!isInitialized) 743 | Initialize(); 744 | 745 | int instanceId = WebSocketAllocate(url); 746 | WebSocket wrapper = new WebSocket(instanceId); 747 | instances.Add(instanceId, wrapper); 748 | 749 | return wrapper; 750 | #else 751 | return new WebSocket(url); 752 | #endif 753 | } 754 | 755 | } 756 | 757 | } --------------------------------------------------------------------------------