From 37acebaf86a779b07b22c701283cd72bd128ec16 Mon Sep 17 00:00:00 2001 From: ItsLJcool Date: Mon, 27 Oct 2025 04:30:01 -0700 Subject: [PATCH 1/7] peak incoming --- .../funkin/backend/system/net/FunkinSocket.hx | 132 ++++++++++++ source/funkin/backend/system/net/Metrics.hx | 29 +++ source/funkin/backend/system/net/Socket.hx | 65 ------ source/funkin/backend/system/net/WebSocket.hx | 201 ++++++++++++++++++ 4 files changed, 362 insertions(+), 65 deletions(-) create mode 100644 source/funkin/backend/system/net/FunkinSocket.hx create mode 100644 source/funkin/backend/system/net/Metrics.hx delete mode 100644 source/funkin/backend/system/net/Socket.hx create mode 100644 source/funkin/backend/system/net/WebSocket.hx diff --git a/source/funkin/backend/system/net/FunkinSocket.hx b/source/funkin/backend/system/net/FunkinSocket.hx new file mode 100644 index 000000000..df91ab2ca --- /dev/null +++ b/source/funkin/backend/system/net/FunkinSocket.hx @@ -0,0 +1,132 @@ +package funkin.backend.system.net; + +#if sys +import sys.net.Host; +import sys.net.Socket as SysSocket; +import haxe.io.Bytes; + +@:keep +class FunkinSocket implements IFlxDestroyable { + public var socket:SysSocket = new SysSocket(); + + public var metrics:Metrics = new Metrics(); + + public var FAST_SEND(default, set):Bool = true; + private function set_FAST_SEND(value:Bool):Bool { + FAST_SEND = value; + socket.setFastSend(value); + return value; + } + public var BLOCKING(default, set):Bool = false; + private function set_BLOCKING(value:Bool):Bool { + BLOCKING = value; + socket.setBlocking(value); + return value; + } + + public var host:Host; + public var port:Int; + + public function new(?_host:String = "127.0.0.1", ?_port:Int = 5000) { + FAST_SEND = true; + BLOCKING = false; + this.host = new Host(_host); + this.port = _port; + } + + // Reading Area + public function readAll():Null { + try { + var bytes = this.socket.input.readAll(); + if (bytes == null) return bytes; + metrics.updateBytesReceived(bytes.length); + return bytes; + } catch(e) { + Logs.traceColored([ + Logs.logText('[FunkinSocket] ', BLUE), + Logs.logText('Failed to read from socket: ${e}', NONE), + ]); + } + return null; + } + public function read(nBytes:Int):Null { + try { + var bytes = this.socket.input.read(nBytes); + if (bytes == null) return bytes; + metrics.updateBytesReceived(bytes.length); + return bytes; + } catch(e) { + Logs.traceColored([ + Logs.logText('[FunkinSocket] ', BLUE), + Logs.logText('Failed to read from socket: ${e}', NONE), + ]); + } + return null; + } + + // Writing Area + public function prepare(nbytes:Int):Void { socket.output.prepare(nbytes); } + public function write(bytes:Bytes):Bool { + try { + this.socket.output.write(bytes); + metrics.updateBytesSent(bytes.length); + return true; + } catch (e) { + Logs.traceColored([ + Logs.logText('[FunkinSocket] ', BLUE), + Logs.logText('Failed to write to socket: ${e}', NONE), + ]); + } + return false; + } + public function writeString(str:String):Bool { + try { + this.socket.output.writeString(str); + metrics.updateBytesSent(Bytes.ofString(str).length); + return true; + } catch(e) { + Logs.traceColored([ + Logs.logText('[FunkinSocket] ', BLUE), + Logs.logText('Failed to write to socket: ${e}', NONE), + ]); + } + return false; + } + + public function bind(?expectingConnections:Int = 1):FunkinSocket { + Logs.traceColored([ + Logs.logText('[FunkinSocket] ', BLUE), + Logs.logText('Binding to ', NONE), Logs.logText(host.toString(), YELLOW), Logs.logText(':', NONE), Logs.logText(Std.string(port), CYAN), + ]); + socket.bind(host, port); + socket.listen(expectingConnections); + return this; + } + + public function connect():FunkinSocket { + Logs.traceColored([ + Logs.logText('[FunkinSocket] ', BLUE), + Logs.logText('Connecting to ', NONE), Logs.logText(host.toString(), YELLOW), Logs.logText(':', NONE), Logs.logText(Std.string(port), CYAN), + ]); + socket.connect(host, port); + return this; + } + + public function close() { + try { + if (socket != null) socket.close(); + Logs.traceColored([ + Logs.logText('[FunkinSocket] ', BLUE), + Logs.logText('Closing socket from', NONE), Logs.logText(host.toString(), YELLOW), Logs.logText(':', NONE), Logs.logText(Std.string(port), CYAN), + ]); + } catch(e) { + Logs.traceColored([ + Logs.logText('[FunkinSocket] ', BLUE), + Logs.logText('Failed to close socket: ${e}', NONE), + ]); + } + } + + public function destroy() { close(); } +} +#end \ No newline at end of file diff --git a/source/funkin/backend/system/net/Metrics.hx b/source/funkin/backend/system/net/Metrics.hx new file mode 100644 index 000000000..0e44aa76d --- /dev/null +++ b/source/funkin/backend/system/net/Metrics.hx @@ -0,0 +1,29 @@ +package funkin.backend.system.net; + +class Metrics { + public var bytesSent:Int = 0; + public var bytesReceived:Int = 0; + + public var packetsSent:Int = 0; + public var packetsReceived:Int = 0; + + public var IS_LOGGING:Bool = true; + + public function new() { } + + public function updateBytesSent(amount:Int) { + if (!IS_LOGGING) return; + bytesSent += amount; + packetsSent++; + } + + public function updateBytesReceived(amount:Int) { + if (!IS_LOGGING) return; + bytesReceived += amount; + packetsReceived++; + } + + public function toString():String { + return '(Metrics) $bytesSent bytes sent | $bytesReceived bytes received | $packetsSent packets sent | $packetsReceived packets received'; + } +} \ No newline at end of file diff --git a/source/funkin/backend/system/net/Socket.hx b/source/funkin/backend/system/net/Socket.hx deleted file mode 100644 index 19ebf9a11..000000000 --- a/source/funkin/backend/system/net/Socket.hx +++ /dev/null @@ -1,65 +0,0 @@ -package funkin.backend.system.net; - -#if sys -import sys.net.Host; -import sys.net.Socket as SysSocket; - -@:keep -class Socket implements IFlxDestroyable { - public var socket:SysSocket; - - public function new(?socket:SysSocket) { - this.socket = socket; - if (this.socket == null) - this.socket = new SysSocket(); - this.socket.setFastSend(true); - this.socket.setBlocking(false); - } - - public function read():String { - try { - return this.socket.input.readUntil('\n'.code).replace("\\n", "\n"); - } catch(e) { - - } - return null; - } - - public function write(str:String):Bool { - try { - this.socket.output.writeString(str.replace("\n", "\\n")); - return true; - } catch(e) { - - } - return false; - } - - public function host(host:Host, port:Int, nbConnections:Int = 1) { - socket.bind(host, port); - socket.listen(nbConnections); - socket.setFastSend(true); - } - - public function hostAndWait(h:Host, port:Int) { - host(h, port); - return acceptConnection(); - } - - public function acceptConnection():Socket { - socket.setBlocking(true); - var accept = new Socket(socket.accept()); - socket.setBlocking(false); - return accept; - } - - public function connect(host:Host, port:Int) { - socket.connect(host, port); - } - - public function destroy() { - if (socket != null) - socket.close(); - } -} -#end \ No newline at end of file diff --git a/source/funkin/backend/system/net/WebSocket.hx b/source/funkin/backend/system/net/WebSocket.hx new file mode 100644 index 000000000..89586f8a0 --- /dev/null +++ b/source/funkin/backend/system/net/WebSocket.hx @@ -0,0 +1,201 @@ +package funkin.backend.system.net; + +import haxe.io.Bytes; +import haxe.io.BytesOutput; + +import haxe.crypto.Base64; +import haxe.crypto.Sha1; + +@:keep +class WebSocket extends FunkinSocket { + public static var CONNECT_PATH:String = "ws"; + public static var HTTP_HEADER:String = "HTTP/1.1"; + + public function new(?_host:String = "127.0.0.1", ?_port:Int = 5000) { + super(_host, _port); + BLOCKING = true; + connect(); + write( + new Header('GET /$CONNECT_PATH $HTTP_HEADER') + .set("Host", '${this.host}:${this.port}') + .set("Upgrade", "websocket") + .set("Connection", "Upgrade") + .set("Sec-WebSocket-Key", getWebSocketKey()) + .set("Sec-WebSocket-Version", "13") + .toBytes() + ); + } + + private function getWebSocketKey():String { + var bytes = Bytes.alloc(16); + for (i in 0...bytes.length) bytes.set(i, Std.int(Math.random() * 256)); + return Base64.encode(bytes); + } +} + +class Header { + public var type:String; + public var fields:Map = new Map(); + + public function new(?type:String = "") { + this.type = type; + } + + public function set(key:String, value:String):Header { + fields.set(key, value); + return this; + } + + inline public function get(key:String):Null { return fields.get(key); } + inline public function keys():Iterator { return fields.keys(); } + + public function toBytes():Bytes { + var bytes = new BytesOutput(); + if (type.length > 0) bytes.writeString('${type}\r\n'); + for (k in keys()) bytes.writeString('$k: ${get(k)}\r\n'); + bytes.writeString('\r\n'); + return bytes.getBytes(); + } +} + +@:enum abstract ResponseCode(Int) from Int to Int { + var CONTINUE = 100; + var SWITCHING_PROTOCOLS = 101; + var PROCESSING = 102; + var EARLY_HINTS = 103; + + var OK = 200; + var CREATED = 201; + var ACCEPTED = 202; + var NON_AUTHORITATIVE_INFORMATION = 203; + var NO_CONTENT = 204; + var RESET_CONTENT = 205; + var PARTIAL_CONTENT = 206; + var MULTI_STATUS = 207; + var ALREADY_REPORTED = 208; + + var IM_USED = 226; + + var MULTIPLE_CHOICES = 300; + var MOVED_PERMANENTLY = 301; + var FOUND = 302; + var SEE_OTHER = 303; + var NOT_MODIFIED = 304; + + var TEMPORARY_REDIRECT = 307; + var PERMANENT_REDIRECT = 308; + + var BAD_REQUEST = 400; + var UNAUTHORIZED = 401; + var PAYMENT_REQUIRED = 402; + var FORBIDDEN = 403; + var NOT_FOUND = 404; + var METHOD_NOT_ALLOWED = 405; + var NOT_ACCEPTABLE = 406; + var PROXY_AUTHENTICATION_REQUIRED = 407; + var REQUEST_TIMEOUT = 408; + var CONFLICT = 409; + var GONE = 410; + var LENGTH_REQUIRED = 411; + var PRECONDITION_FAILED = 412; + var CONTENT_TOO_LARGE = 413; + var URI_TOO_LONG = 414; + var UNSUPPORTED_MEDIA_TYPE = 415; + var RANGE_NOT_SATISFIABLE = 416; + var EXPECTATION_FAILED = 417; + var TEAPOT = 418; + + var MISDIRECTED_REQUEST = 421; + var UNPROCESSABLE_ENTITY = 422; + var LOCKED = 423; + var FAILED_DEPENDENCY = 424; + var TOO_EARLY = 425; + var UPGRADE_REQUIRED = 426; + + var PRECONDITION_REQUIRED = 428; + var TOO_MANY_REQUESTS = 429; + + var REQUEST_HEADER_FIELDS_TOO_LARGE = 431; + + var UNAVAILABLE_FOR_LEGAL_REASONS = 451; + + var INTERNAL_SERVER_ERROR = 500; + var NOT_IMPLEMENTED = 501; + var BAD_GATEWAY = 502; + var SERVICE_UNAVAILABLE = 503; + var GATEWAY_TIMEOUT = 504; + var HTTP_VERSION_NOT_SUPPORTED = 505; + var VARIANT_ALSO_NEGOTIATES = 506; + var INSUFFICIENT_STORAGE = 507; + var LOOP_DETECTED = 508; + + var NOT_EXTENDED = 510; + var NETWORK_AUTHENTICATION_REQUIRED = 511; + + public static inline function toString(code:ResponseCode):String { + return switch (code) + { + case CONTINUE: "Continue"; + case SWITCHING_PROTOCOLS: "Switching Protocols"; + case PROCESSING: "Processing"; + case EARLY_HINTS: "Early Hints"; + case OK: "OK"; + case CREATED: "Created"; + case ACCEPTED: "Accepted"; + case NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information"; + case NO_CONTENT: "No Content"; + case RESET_CONTENT: "Reset Content"; + case PARTIAL_CONTENT: "Partial Content"; + case MULTI_STATUS: "Multi-Status"; + case ALREADY_REPORTED: "Already Reported"; + case IM_USED: "IM Used"; + case MULTIPLE_CHOICES: "Multiple Choices"; + case MOVED_PERMANENTLY: "Moved Permanently"; + case FOUND: "Found"; + case SEE_OTHER: "See Other"; + case NOT_MODIFIED: "Not Modified"; + case TEMPORARY_REDIRECT: "Temporary Redirect"; + case PERMANENT_REDIRECT: "Permanent Redirect"; + case BAD_REQUEST: "Bad Request"; + case UNAUTHORIZED: "Unauthorized"; + case PAYMENT_REQUIRED: "Payment Required"; + case FORBIDDEN: "Forbidden"; + case NOT_FOUND: "Not Found"; + case METHOD_NOT_ALLOWED: "Method Not Allowed"; + case NOT_ACCEPTABLE: "Not Acceptable"; + case PROXY_AUTHENTICATION_REQUIRED: "Proxy Authentication Required"; + case REQUEST_TIMEOUT: "Request Timeout"; + case CONFLICT: "Conflict"; + case GONE: "Gone"; + case LENGTH_REQUIRED: "Length Required"; + case PRECONDITION_FAILED: "Precondition Failed"; + case CONTENT_TOO_LARGE: "Content Too Large"; + case URI_TOO_LONG: "URI Too Long"; + case UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type"; + case RANGE_NOT_SATISFIABLE: "Range Not Satisfiable"; + case EXPECTATION_FAILED: "Expectation Failed"; + case TEAPOT: "I'm a teapot"; + case MISDIRECTED_REQUEST: "Misdirected Request"; + case UNPROCESSABLE_ENTITY: "Unprocessable Entity"; + case LOCKED: "Locked"; + case FAILED_DEPENDENCY: "Failed Dependency"; + case UPGRADE_REQUIRED: "Upgrade Required"; + case PRECONDITION_REQUIRED: "Precondition Required"; + case TOO_MANY_REQUESTS: "Too Many Requests"; + case REQUEST_HEADER_FIELDS_TOO_LARGE: "Request Header Fields Too Large"; + case UNAVAILABLE_FOR_LEGAL_REASONS: "Unavailable For Legal Reasons"; + case INTERNAL_SERVER_ERROR: "Internal Server Error"; + case NOT_IMPLEMENTED: "Not Implemented"; + case BAD_GATEWAY: "Bad Gateway"; + case SERVICE_UNAVAILABLE: "Service Unavailable"; + case GATEWAY_TIMEOUT: "Gateway Timeout"; + case HTTP_VERSION_NOT_SUPPORTED: "HTTP Version Not Supported"; + case VARIANT_ALSO_NEGOTIATES: "Variant Also Negotiates"; + case INSUFFICIENT_STORAGE: "Insufficient Storage"; + case LOOP_DETECTED: "Loop Detected"; + case NOT_EXTENDED: "Not Extended"; + case NETWORK_AUTHENTICATION_REQUIRED: "Network Authentication Required"; + default: "Unknown"; + } + } +} \ No newline at end of file From b0f321e7470996bdecdd9e69441f4995c05a2b2a Mon Sep 17 00:00:00 2001 From: ItsLJcool Date: Mon, 27 Oct 2025 08:10:25 -0700 Subject: [PATCH 2/7] FUCKING HELL I GIVE UP FUCK IT USE hxWebSocket BRUHH --- .../funkin/backend/system/net/FunkinSocket.hx | 51 +++-- source/funkin/backend/system/net/WebSocket.hx | 201 ------------------ 2 files changed, 24 insertions(+), 228 deletions(-) delete mode 100644 source/funkin/backend/system/net/WebSocket.hx diff --git a/source/funkin/backend/system/net/FunkinSocket.hx b/source/funkin/backend/system/net/FunkinSocket.hx index df91ab2ca..ef77fd360 100644 --- a/source/funkin/backend/system/net/FunkinSocket.hx +++ b/source/funkin/backend/system/net/FunkinSocket.hx @@ -38,31 +38,38 @@ class FunkinSocket implements IFlxDestroyable { public function readAll():Null { try { var bytes = this.socket.input.readAll(); - if (bytes == null) return bytes; + if (bytes == null) return null; metrics.updateBytesReceived(bytes.length); return bytes; - } catch(e) { - Logs.traceColored([ - Logs.logText('[FunkinSocket] ', BLUE), - Logs.logText('Failed to read from socket: ${e}', NONE), - ]); - } + } catch(e) { } + return null; + } + public function readLine():Null { + try { + var bytes = this.socket.input.readLine(); + if (bytes == null) return null; + metrics.updateBytesReceived(bytes.length); + return bytes; + } catch(e) { } return null; } public function read(nBytes:Int):Null { try { var bytes = this.socket.input.read(nBytes); - if (bytes == null) return bytes; + if (bytes == null) return null; metrics.updateBytesReceived(bytes.length); return bytes; - } catch(e) { - Logs.traceColored([ - Logs.logText('[FunkinSocket] ', BLUE), - Logs.logText('Failed to read from socket: ${e}', NONE), - ]); - } + } catch(e) { } return null; } + public function readBytes(bytes:Bytes):Int { + try { + var length = this.socket.input.readBytes(bytes, 0, bytes.length); + metrics.updateBytesReceived(length); + return length; + } catch(e) { } + return 0; + } // Writing Area public function prepare(nbytes:Int):Void { socket.output.prepare(nbytes); } @@ -71,12 +78,7 @@ class FunkinSocket implements IFlxDestroyable { this.socket.output.write(bytes); metrics.updateBytesSent(bytes.length); return true; - } catch (e) { - Logs.traceColored([ - Logs.logText('[FunkinSocket] ', BLUE), - Logs.logText('Failed to write to socket: ${e}', NONE), - ]); - } + } catch (e) { } return false; } public function writeString(str:String):Bool { @@ -84,12 +86,7 @@ class FunkinSocket implements IFlxDestroyable { this.socket.output.writeString(str); metrics.updateBytesSent(Bytes.ofString(str).length); return true; - } catch(e) { - Logs.traceColored([ - Logs.logText('[FunkinSocket] ', BLUE), - Logs.logText('Failed to write to socket: ${e}', NONE), - ]); - } + } catch(e) { } return false; } @@ -117,7 +114,7 @@ class FunkinSocket implements IFlxDestroyable { if (socket != null) socket.close(); Logs.traceColored([ Logs.logText('[FunkinSocket] ', BLUE), - Logs.logText('Closing socket from', NONE), Logs.logText(host.toString(), YELLOW), Logs.logText(':', NONE), Logs.logText(Std.string(port), CYAN), + Logs.logText('Closing socket from ', NONE), Logs.logText(host.toString(), YELLOW), Logs.logText(':', NONE), Logs.logText(Std.string(port), CYAN), ]); } catch(e) { Logs.traceColored([ diff --git a/source/funkin/backend/system/net/WebSocket.hx b/source/funkin/backend/system/net/WebSocket.hx deleted file mode 100644 index 89586f8a0..000000000 --- a/source/funkin/backend/system/net/WebSocket.hx +++ /dev/null @@ -1,201 +0,0 @@ -package funkin.backend.system.net; - -import haxe.io.Bytes; -import haxe.io.BytesOutput; - -import haxe.crypto.Base64; -import haxe.crypto.Sha1; - -@:keep -class WebSocket extends FunkinSocket { - public static var CONNECT_PATH:String = "ws"; - public static var HTTP_HEADER:String = "HTTP/1.1"; - - public function new(?_host:String = "127.0.0.1", ?_port:Int = 5000) { - super(_host, _port); - BLOCKING = true; - connect(); - write( - new Header('GET /$CONNECT_PATH $HTTP_HEADER') - .set("Host", '${this.host}:${this.port}') - .set("Upgrade", "websocket") - .set("Connection", "Upgrade") - .set("Sec-WebSocket-Key", getWebSocketKey()) - .set("Sec-WebSocket-Version", "13") - .toBytes() - ); - } - - private function getWebSocketKey():String { - var bytes = Bytes.alloc(16); - for (i in 0...bytes.length) bytes.set(i, Std.int(Math.random() * 256)); - return Base64.encode(bytes); - } -} - -class Header { - public var type:String; - public var fields:Map = new Map(); - - public function new(?type:String = "") { - this.type = type; - } - - public function set(key:String, value:String):Header { - fields.set(key, value); - return this; - } - - inline public function get(key:String):Null { return fields.get(key); } - inline public function keys():Iterator { return fields.keys(); } - - public function toBytes():Bytes { - var bytes = new BytesOutput(); - if (type.length > 0) bytes.writeString('${type}\r\n'); - for (k in keys()) bytes.writeString('$k: ${get(k)}\r\n'); - bytes.writeString('\r\n'); - return bytes.getBytes(); - } -} - -@:enum abstract ResponseCode(Int) from Int to Int { - var CONTINUE = 100; - var SWITCHING_PROTOCOLS = 101; - var PROCESSING = 102; - var EARLY_HINTS = 103; - - var OK = 200; - var CREATED = 201; - var ACCEPTED = 202; - var NON_AUTHORITATIVE_INFORMATION = 203; - var NO_CONTENT = 204; - var RESET_CONTENT = 205; - var PARTIAL_CONTENT = 206; - var MULTI_STATUS = 207; - var ALREADY_REPORTED = 208; - - var IM_USED = 226; - - var MULTIPLE_CHOICES = 300; - var MOVED_PERMANENTLY = 301; - var FOUND = 302; - var SEE_OTHER = 303; - var NOT_MODIFIED = 304; - - var TEMPORARY_REDIRECT = 307; - var PERMANENT_REDIRECT = 308; - - var BAD_REQUEST = 400; - var UNAUTHORIZED = 401; - var PAYMENT_REQUIRED = 402; - var FORBIDDEN = 403; - var NOT_FOUND = 404; - var METHOD_NOT_ALLOWED = 405; - var NOT_ACCEPTABLE = 406; - var PROXY_AUTHENTICATION_REQUIRED = 407; - var REQUEST_TIMEOUT = 408; - var CONFLICT = 409; - var GONE = 410; - var LENGTH_REQUIRED = 411; - var PRECONDITION_FAILED = 412; - var CONTENT_TOO_LARGE = 413; - var URI_TOO_LONG = 414; - var UNSUPPORTED_MEDIA_TYPE = 415; - var RANGE_NOT_SATISFIABLE = 416; - var EXPECTATION_FAILED = 417; - var TEAPOT = 418; - - var MISDIRECTED_REQUEST = 421; - var UNPROCESSABLE_ENTITY = 422; - var LOCKED = 423; - var FAILED_DEPENDENCY = 424; - var TOO_EARLY = 425; - var UPGRADE_REQUIRED = 426; - - var PRECONDITION_REQUIRED = 428; - var TOO_MANY_REQUESTS = 429; - - var REQUEST_HEADER_FIELDS_TOO_LARGE = 431; - - var UNAVAILABLE_FOR_LEGAL_REASONS = 451; - - var INTERNAL_SERVER_ERROR = 500; - var NOT_IMPLEMENTED = 501; - var BAD_GATEWAY = 502; - var SERVICE_UNAVAILABLE = 503; - var GATEWAY_TIMEOUT = 504; - var HTTP_VERSION_NOT_SUPPORTED = 505; - var VARIANT_ALSO_NEGOTIATES = 506; - var INSUFFICIENT_STORAGE = 507; - var LOOP_DETECTED = 508; - - var NOT_EXTENDED = 510; - var NETWORK_AUTHENTICATION_REQUIRED = 511; - - public static inline function toString(code:ResponseCode):String { - return switch (code) - { - case CONTINUE: "Continue"; - case SWITCHING_PROTOCOLS: "Switching Protocols"; - case PROCESSING: "Processing"; - case EARLY_HINTS: "Early Hints"; - case OK: "OK"; - case CREATED: "Created"; - case ACCEPTED: "Accepted"; - case NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information"; - case NO_CONTENT: "No Content"; - case RESET_CONTENT: "Reset Content"; - case PARTIAL_CONTENT: "Partial Content"; - case MULTI_STATUS: "Multi-Status"; - case ALREADY_REPORTED: "Already Reported"; - case IM_USED: "IM Used"; - case MULTIPLE_CHOICES: "Multiple Choices"; - case MOVED_PERMANENTLY: "Moved Permanently"; - case FOUND: "Found"; - case SEE_OTHER: "See Other"; - case NOT_MODIFIED: "Not Modified"; - case TEMPORARY_REDIRECT: "Temporary Redirect"; - case PERMANENT_REDIRECT: "Permanent Redirect"; - case BAD_REQUEST: "Bad Request"; - case UNAUTHORIZED: "Unauthorized"; - case PAYMENT_REQUIRED: "Payment Required"; - case FORBIDDEN: "Forbidden"; - case NOT_FOUND: "Not Found"; - case METHOD_NOT_ALLOWED: "Method Not Allowed"; - case NOT_ACCEPTABLE: "Not Acceptable"; - case PROXY_AUTHENTICATION_REQUIRED: "Proxy Authentication Required"; - case REQUEST_TIMEOUT: "Request Timeout"; - case CONFLICT: "Conflict"; - case GONE: "Gone"; - case LENGTH_REQUIRED: "Length Required"; - case PRECONDITION_FAILED: "Precondition Failed"; - case CONTENT_TOO_LARGE: "Content Too Large"; - case URI_TOO_LONG: "URI Too Long"; - case UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type"; - case RANGE_NOT_SATISFIABLE: "Range Not Satisfiable"; - case EXPECTATION_FAILED: "Expectation Failed"; - case TEAPOT: "I'm a teapot"; - case MISDIRECTED_REQUEST: "Misdirected Request"; - case UNPROCESSABLE_ENTITY: "Unprocessable Entity"; - case LOCKED: "Locked"; - case FAILED_DEPENDENCY: "Failed Dependency"; - case UPGRADE_REQUIRED: "Upgrade Required"; - case PRECONDITION_REQUIRED: "Precondition Required"; - case TOO_MANY_REQUESTS: "Too Many Requests"; - case REQUEST_HEADER_FIELDS_TOO_LARGE: "Request Header Fields Too Large"; - case UNAVAILABLE_FOR_LEGAL_REASONS: "Unavailable For Legal Reasons"; - case INTERNAL_SERVER_ERROR: "Internal Server Error"; - case NOT_IMPLEMENTED: "Not Implemented"; - case BAD_GATEWAY: "Bad Gateway"; - case SERVICE_UNAVAILABLE: "Service Unavailable"; - case GATEWAY_TIMEOUT: "Gateway Timeout"; - case HTTP_VERSION_NOT_SUPPORTED: "HTTP Version Not Supported"; - case VARIANT_ALSO_NEGOTIATES: "Variant Also Negotiates"; - case INSUFFICIENT_STORAGE: "Insufficient Storage"; - case LOOP_DETECTED: "Loop Detected"; - case NOT_EXTENDED: "Not Extended"; - case NETWORK_AUTHENTICATION_REQUIRED: "Network Authentication Required"; - default: "Unknown"; - } - } -} \ No newline at end of file From 86cd114a43266c354343a31db60775c12b056635 Mon Sep 17 00:00:00 2001 From: ItsLJcool Date: Mon, 27 Oct 2025 09:25:43 -0700 Subject: [PATCH 3/7] it just works :trollface: --- building/libs.xml | 1 + project.xml | 2 + .../backend/system/net/FunkinWebSocket.hx | 109 ++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 source/funkin/backend/system/net/FunkinWebSocket.hx diff --git a/building/libs.xml b/building/libs.xml index 139cbaf8a..ee816439a 100644 --- a/building/libs.xml +++ b/building/libs.xml @@ -18,6 +18,7 @@ + diff --git a/project.xml b/project.xml index 3d6399a14..8d5da9ca8 100644 --- a/project.xml +++ b/project.xml @@ -127,6 +127,8 @@ + + diff --git a/source/funkin/backend/system/net/FunkinWebSocket.hx b/source/funkin/backend/system/net/FunkinWebSocket.hx new file mode 100644 index 000000000..6d677eec7 --- /dev/null +++ b/source/funkin/backend/system/net/FunkinWebSocket.hx @@ -0,0 +1,109 @@ +package funkin.backend.system.net; + +import haxe.io.Bytes; +import haxe.io.BytesOutput; + +import flixel.util.FlxSignal.FlxTypedSignal; +import hx.ws.WebSocket; +import hx.ws.Log as LogWs; + +class FunkinWebSocket implements IFlxDestroyable { + private static var LOG_INFO(default, set):Bool = false; + private static function set_LOG_INFO(value:Bool):Bool { + LOG_INFO = value; + if (LOG_INFO) LogWs.mask |= LogWs.INFO; + else LogWs.mask &= ~LogWs.INFO; + return value; + } + private static var LOG_DEBUG(default, set):Bool = false; + private static function set_LOG_DEBUG(value:Bool):Bool { + LOG_DEBUG = value; + if (LOG_DEBUG) LogWs.mask |= LogWs.DEBUG; + else LogWs.mask &= ~LogWs.DEBUG; + return value; + } + private static var LOG_DATA(default, set):Bool = false; + private static function set_LOG_DATA(value:Bool):Bool { + LOG_DATA = value; + if (LOG_DATA) LogWs.mask |= LogWs.DATA; + else LogWs.mask &= ~LogWs.DATA; + return value; + } + + private var _ws:WebSocket; + + public var onOpen:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + public var onMessage:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + public var onClose:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + public var onError:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + + public var url:String; + public function new(_url:String) { + this.url = _url; + this._ws = new WebSocket(this.url, false); + this._ws.onopen = () -> onOpen.dispatch(); + this._ws.onmessage = (message) -> onMessage.dispatch(message); + this._ws.onclose = () -> onClose.dispatch(); + this._ws.onerror = (error) -> onError.dispatch(error); + } + + public function open():FunkinWebSocket { + Logs.traceColored([ + Logs.logText('[FunkinWebSocket] ', CYAN), + Logs.logText('Opening WebSocket to ', NONE), Logs.logText(url, YELLOW), + ]); + _ws.open(); + return this; + } + + public function send(data:Dynamic):Bool { + try { + this._ws.send(data); + } catch(e) { + Logs.traceColored([ + Logs.logText('[FunkinWebSocket] ', CYAN), + Logs.logText('Failed to send data: ${e}', NONE), + ]); + } + return false; + } + + public function close():Void { + Logs.traceColored([ + Logs.logText('[FunkinWebSocket] ', CYAN), + Logs.logText('Closing WebSocket from ', NONE), Logs.logText(url, YELLOW), + ]); + _ws.close(); + } + public function destroy():Void { close(); } +} + +class HeaderWs { + public var head:String; + public var fields:Map = new Map(); + public var content:String; + + public function new(_head:String, ?_content:String = "") { + this.head = _head; + this.content = _content; + } + + public function set(name:String, value:String):HeaderWs { + fields.set(name, value); + return this; + } + inline public function get(name:String):String { return fields.get(name); } + inline public function exists(name:String):Bool { return fields.exists(name); } + inline public function remove(name:String):Bool { return fields.remove(name); } + inline public function keys():Iterator { return fields.keys(); } + + public function toBytes():Bytes { + var bytes:BytesOutput = new BytesOutput(); + if (head.length > 0) bytes.writeString('$head\r\n'); + for (key in keys()) bytes.writeString('$key: ${get(key)}\r\n'); + bytes.writeString('\r\n'); + if (content.length > 0) bytes.writeString('$content\r\n'); + return bytes.getBytes(); + } + public function toString():String { return toBytes().toString(); } +} \ No newline at end of file From 0d4ea8b670c7b1355df0d5f04917ffb10c292033 Mon Sep 17 00:00:00 2001 From: ItsLJcool Date: Thu, 6 Nov 2025 15:37:47 -0700 Subject: [PATCH 4/7] Implementing a way to addon to the Handshake Header --- source/funkin/backend/system/net/FunkinWebSocket.hx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source/funkin/backend/system/net/FunkinWebSocket.hx b/source/funkin/backend/system/net/FunkinWebSocket.hx index 6d677eec7..4f634d057 100644 --- a/source/funkin/backend/system/net/FunkinWebSocket.hx +++ b/source/funkin/backend/system/net/FunkinWebSocket.hx @@ -7,6 +7,9 @@ import flixel.util.FlxSignal.FlxTypedSignal; import hx.ws.WebSocket; import hx.ws.Log as LogWs; +/** +* This is a wrapper for hxWebSockets +**/ class FunkinWebSocket implements IFlxDestroyable { private static var LOG_INFO(default, set):Bool = false; private static function set_LOG_INFO(value:Bool):Bool { @@ -38,8 +41,12 @@ class FunkinWebSocket implements IFlxDestroyable { public var onError:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); public var url:String; + public var handshakeHeaders(get, null):Map; + public function get_handshakeHeaders():Map { return this._ws.additionalHeaders; } + public function new(_url:String) { this.url = _url; + this._ws = new WebSocket(this.url, false); this._ws.onopen = () -> onOpen.dispatch(); this._ws.onmessage = (message) -> onMessage.dispatch(message); @@ -84,8 +91,8 @@ class HeaderWs { public var content:String; public function new(_head:String, ?_content:String = "") { - this.head = _head; - this.content = _content; + this.head = _head.trim(); + this.content = _content.trim(); } public function set(name:String, value:String):HeaderWs { From d0c94d6fe5cacd640a41a786c0a82c45c005c5eb Mon Sep 17 00:00:00 2001 From: ItsLJcool Date: Thu, 6 Nov 2025 16:27:24 -0700 Subject: [PATCH 5/7] so guys, i am the smart, trust :trollface: --- .../funkin/backend/system/net/FunkinWebSocket.hx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/source/funkin/backend/system/net/FunkinWebSocket.hx b/source/funkin/backend/system/net/FunkinWebSocket.hx index 4f634d057..838ef93d8 100644 --- a/source/funkin/backend/system/net/FunkinWebSocket.hx +++ b/source/funkin/backend/system/net/FunkinWebSocket.hx @@ -104,13 +104,17 @@ class HeaderWs { inline public function remove(name:String):Bool { return fields.remove(name); } inline public function keys():Iterator { return fields.keys(); } + public function toString():String { + var str:String = ''; + if (head.length > 0) str += '${head}\r\n'; + for (key in keys()) str += '$key: ${get(key)}\r\n'; + str += '\r\n'; + if (content.length > 0) str += '${content}\r\n'; + return str; + } public function toBytes():Bytes { var bytes:BytesOutput = new BytesOutput(); - if (head.length > 0) bytes.writeString('$head\r\n'); - for (key in keys()) bytes.writeString('$key: ${get(key)}\r\n'); - bytes.writeString('\r\n'); - if (content.length > 0) bytes.writeString('$content\r\n'); + bytes.writeString(toString()); // Absolute Cinema, thanks AbstractAndrew for the Revolutionary Idea 🔥🔥 return bytes.getBytes(); } - public function toString():String { return toBytes().toString(); } } \ No newline at end of file From 59a0901b402424654dce4310de1392a9a9723db2 Mon Sep 17 00:00:00 2001 From: ItsLJcool Date: Fri, 7 Nov 2025 18:03:28 -0700 Subject: [PATCH 6/7] Properly converts the message from the server into either String, Bytes, FunkinPacket. --- .../funkin/backend/system/net/FunkinPacket.hx | 76 +++++++++++++++++++ .../backend/system/net/FunkinWebSocket.hx | 60 ++++++--------- 2 files changed, 98 insertions(+), 38 deletions(-) create mode 100644 source/funkin/backend/system/net/FunkinPacket.hx diff --git a/source/funkin/backend/system/net/FunkinPacket.hx b/source/funkin/backend/system/net/FunkinPacket.hx new file mode 100644 index 000000000..6c1c9c0d4 --- /dev/null +++ b/source/funkin/backend/system/net/FunkinPacket.hx @@ -0,0 +1,76 @@ +package funkin.backend.system.net; + +import flixel.util.typeLimit.OneOfTwo; + +import haxe.io.Bytes; +import haxe.io.BytesOutput; + +class FunkinPacket { + public var status:Int = -1; + public var head:String; + public var fields:Map = new Map(); + public var body:OneOfTwo; + + public function new(_head:String, ?_body:OneOfTwo = "", ?_status:Int = -1) { + this.head = _head.trim(); + this.body = (_body is String) ? _body.trim() : _body; + this.status = _status; + } + + public function set(name:String, value:String):FunkinPacket { fields.set(name, value); return this; } + inline public function get(name:String):String { return fields.get(name); } + inline public function exists(name:String):Bool { return fields.exists(name); } + inline public function remove(name:String):Bool { return fields.remove(name); } + inline public function keys():Iterator { return fields.keys(); } + + public function toString(?includeBody:Bool = true):String { + var str:String = ''; + if (head.length > 0) str += '$head\r\n'; + for (key in keys()) str += '$key: ${get(key)}\r\n'; + if (!includeBody) return str; + if (body is String) str += body; + else str += (cast body : Bytes).toString(); + return str; + } + public function toBytes():Bytes { + var bytes:BytesOutput = new BytesOutput(); + bytes.writeString(toString(false)); // Absolute Cinema, thanks AbstractAndrew for the Revolutionary Idea 🔥🔥 + if (body is String) bytes.writeString(body); + else if (body is Bytes) bytes.write(body); + return bytes.getBytes(); + } + + public static function fromBytes(bytes:Bytes):Null { + var status:Int = -1; + + var header_length:Int = -1; + var header:String = ""; + + var body_is_string:Bool = false; + var body_length:Int = -1; + var body:OneOfTwo = null; + + try { + var offset:Int = 0; + status = bytes.getInt32(0); offset += 4; + header_length = bytes.getInt32(offset); offset += 4; + header = bytes.getString(offset, header_length); offset += header_length; + body_is_string = (bytes.get(offset) == 1); offset += 1; + body_length = bytes.getInt32(offset); offset += 4; + if (body_is_string) body = bytes.getString(offset, body_length); + else body = bytes.sub(offset, body_length); + } catch(e) { + FlxG.log.error('FunkinPacket.fromBytes() failed to parse packet: $e'); + return null; + } + var packet:FunkinPacket = new FunkinPacket(null, body, status); + for (line in header.split("\r\n")) { + var data = line.split(": "); + if (data.length < 2) continue; + var key:String = data.shift().trim(); + var value:String = data.shift().trim(); + packet.set(key, value); + } + return packet; + } +} \ No newline at end of file diff --git a/source/funkin/backend/system/net/FunkinWebSocket.hx b/source/funkin/backend/system/net/FunkinWebSocket.hx index 838ef93d8..66fe1f488 100644 --- a/source/funkin/backend/system/net/FunkinWebSocket.hx +++ b/source/funkin/backend/system/net/FunkinWebSocket.hx @@ -1,11 +1,13 @@ package funkin.backend.system.net; +import flixel.util.typeLimit.OneOfThree; + import haxe.io.Bytes; -import haxe.io.BytesOutput; import flixel.util.FlxSignal.FlxTypedSignal; -import hx.ws.WebSocket; import hx.ws.Log as LogWs; +import hx.ws.WebSocket; +import hx.ws.Types.MessageType; /** * This is a wrapper for hxWebSockets @@ -36,7 +38,7 @@ class FunkinWebSocket implements IFlxDestroyable { private var _ws:WebSocket; public var onOpen:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); - public var onMessage:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + public var onMessage:FlxTypedSignal->Void> = new FlxTypedSignal->Void>(); // cursed 😭😭 public var onClose:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); public var onError:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); @@ -44,12 +46,27 @@ class FunkinWebSocket implements IFlxDestroyable { public var handshakeHeaders(get, null):Map; public function get_handshakeHeaders():Map { return this._ws.additionalHeaders; } + public var AUTO_DECODE_PACKETS:Bool = true; + public function new(_url:String) { this.url = _url; this._ws = new WebSocket(this.url, false); this._ws.onopen = () -> onOpen.dispatch(); - this._ws.onmessage = (message) -> onMessage.dispatch(message); + this._ws.onmessage = (message:MessageType) -> { + var data:OneOfThree = ""; + switch(message) { + case StrMessage(content): + data = content; + case BytesMessage(buffer): + data = buffer.readAllAvailableBytes(); + if (!AUTO_DECODE_PACKETS) return onMessage.dispatch(data); + var packet:FunkinPacket = FunkinPacket.fromBytes(data); + if (packet == null) return onMessage.dispatch(data); + data = packet; + } + onMessage.dispatch(data); + }; this._ws.onclose = () -> onClose.dispatch(); this._ws.onerror = (error) -> onError.dispatch(error); } @@ -66,6 +83,7 @@ class FunkinWebSocket implements IFlxDestroyable { public function send(data:Dynamic):Bool { try { this._ws.send(data); + return true; } catch(e) { Logs.traceColored([ Logs.logText('[FunkinWebSocket] ', CYAN), @@ -83,38 +101,4 @@ class FunkinWebSocket implements IFlxDestroyable { _ws.close(); } public function destroy():Void { close(); } -} - -class HeaderWs { - public var head:String; - public var fields:Map = new Map(); - public var content:String; - - public function new(_head:String, ?_content:String = "") { - this.head = _head.trim(); - this.content = _content.trim(); - } - - public function set(name:String, value:String):HeaderWs { - fields.set(name, value); - return this; - } - inline public function get(name:String):String { return fields.get(name); } - inline public function exists(name:String):Bool { return fields.exists(name); } - inline public function remove(name:String):Bool { return fields.remove(name); } - inline public function keys():Iterator { return fields.keys(); } - - public function toString():String { - var str:String = ''; - if (head.length > 0) str += '${head}\r\n'; - for (key in keys()) str += '$key: ${get(key)}\r\n'; - str += '\r\n'; - if (content.length > 0) str += '${content}\r\n'; - return str; - } - public function toBytes():Bytes { - var bytes:BytesOutput = new BytesOutput(); - bytes.writeString(toString()); // Absolute Cinema, thanks AbstractAndrew for the Revolutionary Idea 🔥🔥 - return bytes.getBytes(); - } } \ No newline at end of file From 8b3cd659f4d6207eed92491428d92c86dfd28053 Mon Sep 17 00:00:00 2001 From: ItsLJcool Date: Fri, 7 Nov 2025 18:39:29 -0700 Subject: [PATCH 7/7] Documentation on `FunkinWebSocket` and added `Metrics` to it :) --- .../backend/system/net/FunkinWebSocket.hx | 84 ++++++++++++++++++- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/source/funkin/backend/system/net/FunkinWebSocket.hx b/source/funkin/backend/system/net/FunkinWebSocket.hx index 66fe1f488..4214bcb56 100644 --- a/source/funkin/backend/system/net/FunkinWebSocket.hx +++ b/source/funkin/backend/system/net/FunkinWebSocket.hx @@ -10,9 +10,16 @@ import hx.ws.WebSocket; import hx.ws.Types.MessageType; /** -* This is a wrapper for hxWebSockets +* This is a wrapper for hxWebSockets. Used in-tangem with `FunkinPacket` and `Metrics`. +* By default, it assums the Connections is to a [CodenameEngine Template Server](https://github.com/ItsLJcool/CodenameEngine-Online-Template). +* You can override how `haxe.io.Bytes` is decoded by setting `AUTO_DECODE_PACKETS`. By default it will attempt to deserialize the packet into a `FunkinPacket`. +* It also has `Metrics` which keeps track of the amount of bytes sent and received. **/ class FunkinWebSocket implements IFlxDestroyable { + /** + * This interacts with the hxWebSockets logging system, probably the best way to get the debug info. + * Although, it's not in the format of CodenameEngine's logs so it might look weird. + **/ private static var LOG_INFO(default, set):Bool = false; private static function set_LOG_INFO(value:Bool):Bool { LOG_INFO = value; @@ -20,6 +27,9 @@ class FunkinWebSocket implements IFlxDestroyable { else LogWs.mask &= ~LogWs.INFO; return value; } + /** + * Ditto to LOG_INFO + **/ private static var LOG_DEBUG(default, set):Bool = false; private static function set_LOG_DEBUG(value:Bool):Bool { LOG_DEBUG = value; @@ -27,6 +37,9 @@ class FunkinWebSocket implements IFlxDestroyable { else LogWs.mask &= ~LogWs.DEBUG; return value; } + /** + * Ditto to LOG_INFO + **/ private static var LOG_DATA(default, set):Bool = false; private static function set_LOG_DATA(value:Bool):Bool { LOG_DATA = value; @@ -35,19 +48,61 @@ class FunkinWebSocket implements IFlxDestroyable { return value; } - private var _ws:WebSocket; + @:dox(hide) private var _ws:WebSocket; + /** + * This keeps track of the amount of bytes sent and received. + * You can set the logging state directly in the class. + **/ + public var metrics:Metrics = new Metrics(); + + /** + * This signal is only called once when the connection is opened. + **/ public var onOpen:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + /** + * This signal is called every time a message is received. + * It can be one of three types: String, Bytes, or FunkinPacket. + * If you have AUTO_DECODE_PACKETS set to true, It will attempt to deserialize the packet into a FunkinPacket. + * If it fails to deserialize or AUTO_DECODE_PACKETS is false, it will just return the Bytes directly. + **/ public var onMessage:FlxTypedSignal->Void> = new FlxTypedSignal->Void>(); // cursed 😭😭 + /** + * This signal is only called once when the connection is closed. + **/ public var onClose:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + /** + * This signal is only called when an error occurs. Useful for debugging and letting the user know something has gone wrong. + **/ public var onError:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + /** + * The URL to connect to, including the protocol (ws:// or wss://). Currently wss:// (SSH) is not supported. + **/ public var url:String; + + /** + * This just allows you to override or add custom headers when the handshake happens, as this is the first and last time we use proper HTTP Headers. + **/ public var handshakeHeaders(get, null):Map; public function get_handshakeHeaders():Map { return this._ws.additionalHeaders; } + /** + * Since not all servers are going to be the Custom CodenameEngine Template Server, this allows you to receive the packet as raw Bytes, if you want to decode it yourself. + * Not all incomming data will be bytes, since Strings are just... strings, there is no reason to have special handling for them. + **/ public var AUTO_DECODE_PACKETS:Bool = true; + /** + * This is only called if the `Metrics` failed to update the bytes sent or received. + * So you can handle and update the data yourself. + * If Bool is true, the data was being sent. Otherwise it was being received. + **/ + private var updateMetricCustom:(Metrics, Bool, Dynamic)->Void = null; + + /** + * @param _url The URL to connect to, including the protocol (ws:// or wss://). Currently wss:// (SSH) is not supported. + **/ public function new(_url:String) { this.url = _url; @@ -58,7 +113,9 @@ class FunkinWebSocket implements IFlxDestroyable { switch(message) { case StrMessage(content): data = content; + metrics.updateBytesReceived(Bytes.ofString(content).length); case BytesMessage(buffer): + metrics.updateBytesReceived(buffer.length); data = buffer.readAllAvailableBytes(); if (!AUTO_DECODE_PACKETS) return onMessage.dispatch(data); var packet:FunkinPacket = FunkinPacket.fromBytes(data); @@ -71,18 +128,29 @@ class FunkinWebSocket implements IFlxDestroyable { this._ws.onerror = (error) -> onError.dispatch(error); } + /** + * Opens the WebSocket connection. + **/ public function open():FunkinWebSocket { Logs.traceColored([ Logs.logText('[FunkinWebSocket] ', CYAN), Logs.logText('Opening WebSocket to ', NONE), Logs.logText(url, YELLOW), ]); - _ws.open(); + this._ws.open(); return this; } + /** + * Sends data to the server. + * @param data The data to send. + **/ public function send(data:Dynamic):Bool { try { this._ws.send(data); + if (data is String) metrics.updateBytesSent(Bytes.ofString(data).length); + else if (data is Bytes) metrics.updateBytesSent(data.length); + else if (data is FunkinPacket) metrics.updateBytesSent(data.toBytes().length); + else if (metrics.IS_LOGGING && updateMetricCustom != null) updateMetricCustom(metrics, true, data); return true; } catch(e) { Logs.traceColored([ @@ -93,12 +161,20 @@ class FunkinWebSocket implements IFlxDestroyable { return false; } + /** + * Closes the WebSocket connection. + * Once you close the connection, you cannot reopen it, you must create a new instance. + **/ public function close():Void { Logs.traceColored([ Logs.logText('[FunkinWebSocket] ', CYAN), Logs.logText('Closing WebSocket from ', NONE), Logs.logText(url, YELLOW), ]); - _ws.close(); + this._ws.close(); } + + /** + * Basically the same as close(), but if a class is handling it and expects it to be IFlxDestroyable compatable, it will call this. + **/ public function destroy():Void { close(); } } \ No newline at end of file