@@ -11,6 +11,10 @@ import helder.Set;
1111import hx .ws .Types .MessageType ;
1212import hx .ws .WebSocket ;
1313import haxe .Json as TJson ;
14+ import haxe .zip .Uncompress ;
15+ import haxe .io .Bytes ;
16+ import deflatex .* ;
17+ import yutautil .PermessageDeflate ;
1418import backend .ClientPrefs ;
1519#if sys
1620import sys .FileSystem ;
@@ -1317,7 +1321,7 @@ class Client {
13171321 if (_sendQueue .length > 0 ) {
13181322 _sendLock .execute (() -> {
13191323 trace (' Sending ${_sendQueue .length } queued packet(s)' );
1320- _ws .send (haxe.io. Bytes . ofString (TJson .stringify (_sendQueue )));
1324+ _ws .send ((TJson .stringify (_sendQueue )));
13211325 _sendQueue = [];
13221326 });
13231327 }
@@ -1692,21 +1696,202 @@ class Client {
16921696 case StrMessage (content ):
16931697 _recvLock .execute (() -> {
16941698 try {
1695- var newPackets : Array <IncomingPacket > = TJson .parse (content );
1696- // trace(newPackets);
1699+ // Comprehensive debugging of the received data
1700+ trace (" === RAW MESSAGE ANALYSIS ===" );
1701+ trace (" Content length: " + content .length );
1702+ // trace("First 100 chars: " + content.substr(0, 100));
1703+
1704+ // // Show character codes for first 20 characters
1705+ // var charCodes = [];
1706+ // for (i in 0...Std.int(Math.min(20, content.length))) {
1707+ // charCodes.push(content.charCodeAt(i));
1708+ // }
1709+ // trace("First 20 char codes: " + charCodes.join(", "));
1710+
1711+ // // Convert to bytes and show hex
1712+ // var contentBytes = haxe.io.Bytes.ofString(content, haxe.io.Encoding.RawNative);
1713+ // var hexBytes = [];
1714+ // for (i in 0...Std.int(Math.min(20, contentBytes.length))) {
1715+ // hexBytes.push(StringTools.hex(contentBytes.get(i), 2));
1716+ // }
1717+ // trace("First 20 bytes (hex): " + hexBytes.join(" "));
1718+
1719+ // Check if it's already valid JSON
1720+ var trimmed = StringTools .trim (content );
1721+ if (trimmed .startsWith (" [" ) || trimmed .startsWith (" {" )) {
1722+ trace (" Data appears to be uncompressed JSON" );
1723+ var newPackets : Array <IncomingPacket > = TJson .parse (content );
1724+ for (newPacket in newPackets )
1725+ _recvQueue .push (newPacket );
1726+ trace (' === END OF RAW MESSAGE ANALYSIS ===' );
1727+ return ;
1728+ }
1729+
1730+ // Check for common compression signatures
1731+ var first2Bytes = contentBytes .length >= 2 ? (contentBytes .get (0 ) << 8 ) | contentBytes .get (1 ) : 0 ;
1732+ trace (" First 2 bytes as uint16: 0x" + StringTools .hex (first2Bytes , 4 ));
1733+
1734+ // Check for various compression formats
1735+ if (contentBytes .length >= 2 ) {
1736+ var b1 = contentBytes .get (0 );
1737+ var b2 = contentBytes .get (1 );
1738+
1739+ if (b1 == 0x78 && (b2 == 0x01 || b2 == 0x5E || b2 == 0x9C || b2 == 0xDA )) {
1740+ trace (" Detected zlib header" );
1741+ } else if (b1 == 0x1F && b2 == 0x8B ) {
1742+ trace (" Detected gzip header" );
1743+ } else if (b1 == 0x08 || b1 == 0x01 ) {
1744+ trace (" Possible deflate block header" );
1745+ } else {
1746+ trace (" Unknown compression format or uncompressed data" );
1747+ }
1748+ }
1749+
1750+ // Try multiple decompression approaches
1751+ var approaches = [
1752+ // Approach 1: Try as-is if it looks like JSON
1753+ function (): String {
1754+ trace (" Approach 1: Direct parsing" );
1755+ if (content .indexOf (' "' ) >= 0 || content .indexOf (' [' ) >= 0 || content .indexOf (' {' ) >= 0 ) {
1756+ return content ;
1757+ }
1758+ return null ;
1759+ },
1760+
1761+ // Approach 2: Our custom permessage-deflate
1762+ function (): String {
1763+ trace (" Approach 2: PermessageDeflate" );
1764+ return PermessageDeflate .smartDecompress (content );
1765+ },
1766+
1767+ // Approach 3: Raw deflate with Haxe's implementation
1768+ function (): String {
1769+ trace (" Approach 3: Raw deflate" );
1770+ try {
1771+ var input = new haxe.io. StringInput (content );
1772+ var inflater = new haxe.zip. InflateImpl (input , false , false );
1773+ var output = new haxe.io. BytesOutput ();
1774+ @:privateAccess
1775+ inflater .output = output .getBytes ();
1776+ var output = haxe.zip. InflateImpl .run (input );
1777+ return output .toString ();
1778+ } catch (e : Dynamic ) {
1779+ trace (" Raw deflate failed: " + e );
1780+ return null ;
1781+ }
1782+ },
1783+
1784+ // Approach 4: Try base64 decode first
1785+ function (): String {
1786+ trace (" Approach 4: Base64 decode" );
1787+ try {
1788+ var decoded = haxe.crypto. Base64 .decode (content );
1789+ return decoded .toString ();
1790+ } catch (e : Dynamic ) {
1791+ trace (" Base64 decode failed: " + e );
1792+ return null ;
1793+ }
1794+ },
1795+
1796+ // Approach 5: Try URL decode
1797+ function (): String {
1798+ trace (" Approach 5: URL decode" );
1799+ try {
1800+ var decoded = StringTools .urlDecode (content );
1801+ if (decoded != content ) {
1802+ return decoded ;
1803+ }
1804+ return null ;
1805+ } catch (e : Dynamic ) {
1806+ trace (" URL decode failed: " + e );
1807+ return null ;
1808+ }
1809+ },
1810+
1811+ // Approach 6: Try treating each character as a byte
1812+ function (): String {
1813+ trace (" Approach 6: Character-to-byte conversion" );
1814+ try {
1815+ var bytes = haxe.io. Bytes .alloc (content .length );
1816+ for (i in 0 ... content .length ) {
1817+ bytes .set (i , content .charCodeAt (i ) & 0xFF );
1818+ }
1819+ return bytes .toString ();
1820+ } catch (e : Dynamic ) {
1821+ trace (" Char-to-byte failed: " + e );
1822+ return null ;
1823+ }
1824+ }
1825+ ];
1826+
1827+ var decompressedContent : String = null ;
1828+ for (i in 0 ... approaches .length ) {
1829+ try {
1830+ var result = approaches [i ]();
1831+ if (result != null && result != content ) {
1832+ trace (" Approach " + (i + 1 ) + " produced different result (length: " + result .length + " )" );
1833+ trace (" Result preview: " + result .substr (0 , 100 ));
1834+
1835+ // Check if result looks like valid JSON
1836+ var trimmedResult = StringTools .trim (result );
1837+ if (trimmedResult .startsWith (" [" ) || trimmedResult .startsWith (" {" )) {
1838+ trace (" Approach " + (i + 1 ) + " produced valid JSON!" );
1839+ decompressedContent = result ;
1840+ break ;
1841+ }
1842+ } else if (result != null ) {
1843+ trace (" Approach " + (i + 1 ) + " returned same content" );
1844+ } else {
1845+ trace (" Approach " + (i + 1 ) + " returned null" );
1846+ }
1847+ } catch (e : Dynamic ) {
1848+ trace (" Approach " + (i + 1 ) + " failed: " + e );
1849+ }
1850+ }
1851+
1852+ // If nothing worked, use original content
1853+ if (decompressedContent == null ) {
1854+ trace (" All decompression approaches failed, using original content" );
1855+ decompressedContent = content ;
1856+ }
1857+
1858+ trace (" Final content preview: " + decompressedContent .substr (0 , 100 ));
1859+ trace (" === END ANALYSIS ===" );
1860+
1861+ var newPackets : Array <IncomingPacket > = TJson .parse (decompressedContent );
16971862 for (newPacket in newPackets )
16981863 _recvQueue .push (newPacket );
16991864 } catch (e ) {
17001865 trace (" EXCEPTION onmessage: " + e );
17011866 _hOnThrow (" onmessage" , e );
1867+ trace (" Content: " + content );
17021868 }
17031869 });
17041870
17051871 case BytesMessage (bytes ):
17061872 _recvLock .execute (() -> {
17071873 try {
1708- var content = bytes .readAllAvailableBytes ().toString ();
1709- var newPackets : Array <IncomingPacket > = TJson .parse (content );
1874+ var rawBytes = bytes .readAllAvailableBytes ();
1875+ var decompressedContent : String ;
1876+
1877+ try {
1878+ // Try to decompress the bytes using our custom permessage-deflate handler
1879+ var uncompressed = PermessageDeflate .decompress (rawBytes );
1880+ if (uncompressed != null ) {
1881+ decompressedContent = uncompressed .toString ();
1882+ trace (" Successfully decompressed bytes message using PermessageDeflate (length: " + rawBytes .length + " -> " + decompressedContent .length + " )" );
1883+ } else {
1884+ // If our handler fails, treat as plain text
1885+ decompressedContent = rawBytes .toString ();
1886+ trace (" PermessageDeflate returned null, using raw bytes as string" );
1887+ }
1888+ } catch (e : Dynamic ) {
1889+ // If all decompression fails, treat as plain text
1890+ trace (" All decompression failed for bytes message: " + e );
1891+ decompressedContent = rawBytes .toString ();
1892+ }
1893+
1894+ var newPackets : Array <IncomingPacket > = TJson .parse (decompressedContent );
17101895 // trace(newPackets);
17111896 for (newPacket in newPackets )
17121897 _recvQueue .push (newPacket );
@@ -1751,12 +1936,17 @@ class Client {
17511936 _socketReconnectInterval = 15 ;
17521937 connectAttempts ++ ;
17531938
1754- _ws = new WebSocket (uri );
1939+ _ws = new WebSocket (uri , false , new OneOrMany < String > ( ' permessage-deflate ' ) );
17551940 _ws .onopen = onopen ;
17561941 _ws .onclose = onclose ;
17571942 _ws .onmessage = onmessage ;
17581943 _ws .onerror = onerror ;
1944+ if (ClientPrefs .data .apCompressed )
1945+ _ws .additionalHeaders .set (' Sec-WebSocket-Extensions' , ' permessage-deflate' );
1946+ _ws .open ();
1947+ trace (" Using headers: " + _ws .additionalHeaders );
17591948 } catch (e : Exception ) {
1949+ trace (" Error Details: " + new DetailedException (e ));
17601950 trace (" Error connecting to AP socket" , e );
17611951 _hOnThrow (" connect_socket" , e );
17621952 if (e .message == " ssl network error" && uri .startsWith (" wss:" )) {
0 commit comments