@naeemo/capnp 0.9.0 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -105,6 +105,13 @@ function encodeStructPointer(offset, dataWords, pointerCount) {
105
105
  function encodeListPointer(offset, elementSize, elementCount) {
106
106
  return (BigInt(offset < 0 ? offset + 1073741824 : offset) & BigInt(1073741823)) << BigInt(2) | BigInt(1) | BigInt(elementSize) << BigInt(32) | BigInt(elementCount) << BigInt(35);
107
107
  }
108
+ /**
109
+ * 编码 Far 指针
110
+ */
111
+ function encodeFarPointer(segment, offset, doubleFar = false) {
112
+ const offsetBits = BigInt(offset) & BigInt(536870911);
113
+ return (BigInt(segment) & BigInt(4294967295)) << BigInt(32) | offsetBits << BigInt(3) | (doubleFar ? BigInt(1) << BigInt(2) : BigInt(0)) | BigInt(2);
114
+ }
108
115
 
109
116
  //#endregion
110
117
  //#region src/core/segment.ts
@@ -549,21 +556,38 @@ var ListBuilder = class {
549
556
  * Cap'n Proto MessageReader
550
557
  * 纯 TypeScript 实现
551
558
  */
559
+ /** 默认安全选项 */
560
+ const DEFAULT_SECURITY_OPTIONS = {
561
+ maxSegments: 64,
562
+ maxTotalSize: 64 * 1024 * 1024,
563
+ strictMode: false
564
+ };
552
565
  var MessageReader = class {
553
566
  segments;
554
- constructor(buffer) {
567
+ securityOptions;
568
+ constructor(buffer, securityOptions) {
569
+ this.securityOptions = {
570
+ ...DEFAULT_SECURITY_OPTIONS,
571
+ ...securityOptions
572
+ };
555
573
  const uint8Array = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer;
556
574
  this.segments = [];
575
+ if (this.securityOptions.strictMode && uint8Array.byteLength > this.securityOptions.maxTotalSize) throw new Error(`Message size (${uint8Array.byteLength} bytes) exceeds maximum allowed size (${this.securityOptions.maxTotalSize} bytes)`);
557
576
  if (uint8Array.byteLength < 8) return;
558
577
  const view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
559
578
  const firstWordLow = view.getUint32(0, true);
560
579
  const firstWordHigh = view.getUint32(4, true);
561
580
  const segmentCount = (firstWordLow & 4294967295) + 1;
562
581
  const firstSegmentSize = firstWordHigh;
582
+ if (segmentCount > this.securityOptions.maxSegments) {
583
+ if (this.securityOptions.strictMode) throw new Error(`Segment count (${segmentCount}) exceeds maximum allowed (${this.securityOptions.maxSegments})`);
584
+ return;
585
+ }
563
586
  let offset = 8;
564
587
  const segmentSizes = [firstSegmentSize];
565
588
  for (let i = 1; i < segmentCount; i++) {
566
589
  if (offset + 4 > uint8Array.byteLength) {
590
+ if (this.securityOptions.strictMode) throw new Error(`Message ended prematurely while reading segment ${i} size (offset: ${offset}, length: ${uint8Array.byteLength})`);
567
591
  this.segments = [];
568
592
  return;
569
593
  }
@@ -572,12 +596,24 @@ var MessageReader = class {
572
596
  }
573
597
  offset = offset + 7 & -8;
574
598
  if (offset > uint8Array.byteLength) {
599
+ if (this.securityOptions.strictMode) throw new Error(`Insufficient data for segment table (offset: ${offset}, length: ${uint8Array.byteLength})`);
575
600
  this.segments = [];
576
601
  return;
577
602
  }
603
+ const headerSize = offset;
604
+ let totalBodySize = 0;
605
+ for (const size of segmentSizes) totalBodySize += size * WORD_SIZE;
606
+ const expectedTotalSize = headerSize + totalBodySize;
607
+ if (expectedTotalSize > this.securityOptions.maxTotalSize) {
608
+ if (this.securityOptions.strictMode) throw new Error(`Total message size (${expectedTotalSize} bytes) exceeds maximum allowed size (${this.securityOptions.maxTotalSize} bytes)`);
609
+ return;
610
+ }
578
611
  this.segments = [];
579
612
  for (const size of segmentSizes) {
580
- if (offset + size * WORD_SIZE > uint8Array.byteLength) break;
613
+ if (offset + size * WORD_SIZE > uint8Array.byteLength) {
614
+ if (this.securityOptions.strictMode) throw new Error(`Insufficient data for segment (expected ${size * WORD_SIZE} bytes at offset ${offset}, but message is ${uint8Array.byteLength} bytes)`);
615
+ break;
616
+ }
581
617
  const segmentBuffer = uint8Array.slice(offset, offset + size * WORD_SIZE);
582
618
  this.segments.push(Segment.fromBuffer(segmentBuffer.buffer));
583
619
  offset += size * WORD_SIZE;
@@ -671,6 +707,12 @@ var MessageReader = class {
671
707
  get segmentCount() {
672
708
  return this.segments.length;
673
709
  }
710
+ /**
711
+ * 获取当前安全选项配置
712
+ */
713
+ getSecurityOptions() {
714
+ return { ...this.securityOptions };
715
+ }
674
716
  };
675
717
  /**
676
718
  * 结构读取器
@@ -1654,6 +1696,18 @@ var WebSocketTransport = class WebSocketTransport {
1654
1696
  get connected() {
1655
1697
  return this._connected;
1656
1698
  }
1699
+ getCompressionState() {
1700
+ return {
1701
+ enabled: false,
1702
+ algorithm: "none",
1703
+ bytesSent: 0,
1704
+ bytesReceived: 0,
1705
+ uncompressedBytesSent: 0,
1706
+ uncompressedBytesReceived: 0,
1707
+ messagesCompressed: 0,
1708
+ messagesDecompressed: 0
1709
+ };
1710
+ }
1657
1711
  connect(url) {
1658
1712
  this.ws = new WebSocket(url);
1659
1713
  this.ws.binaryType = this.options.binaryType ?? "arraybuffer";
@@ -1810,6 +1864,432 @@ var WebSocketTransport = class WebSocketTransport {
1810
1864
  }
1811
1865
  };
1812
1866
 
1867
+ //#endregion
1868
+ //#region node_modules/.pnpm/lz4js@0.2.0/node_modules/lz4js/util.js
1869
+ var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
1870
+ exports.hashU32 = function hashU32(a) {
1871
+ a = a | 0;
1872
+ a = a + 2127912214 + (a << 12) | 0;
1873
+ a = a ^ -949894596 ^ a >>> 19;
1874
+ a = a + 374761393 + (a << 5) | 0;
1875
+ a = a + -744332180 ^ a << 9;
1876
+ a = a + -42973499 + (a << 3) | 0;
1877
+ return a ^ -1252372727 ^ a >>> 16 | 0;
1878
+ };
1879
+ exports.readU64 = function readU64(b, n) {
1880
+ var x = 0;
1881
+ x |= b[n++] << 0;
1882
+ x |= b[n++] << 8;
1883
+ x |= b[n++] << 16;
1884
+ x |= b[n++] << 24;
1885
+ x |= b[n++] << 32;
1886
+ x |= b[n++] << 40;
1887
+ x |= b[n++] << 48;
1888
+ x |= b[n++] << 56;
1889
+ return x;
1890
+ };
1891
+ exports.readU32 = function readU32(b, n) {
1892
+ var x = 0;
1893
+ x |= b[n++] << 0;
1894
+ x |= b[n++] << 8;
1895
+ x |= b[n++] << 16;
1896
+ x |= b[n++] << 24;
1897
+ return x;
1898
+ };
1899
+ exports.writeU32 = function writeU32(b, n, x) {
1900
+ b[n++] = x >> 0 & 255;
1901
+ b[n++] = x >> 8 & 255;
1902
+ b[n++] = x >> 16 & 255;
1903
+ b[n++] = x >> 24 & 255;
1904
+ };
1905
+ exports.imul = function imul(a, b) {
1906
+ var ah = a >>> 16;
1907
+ var al = a & 65535;
1908
+ var bh = b >>> 16;
1909
+ var bl = b & 65535;
1910
+ return al * bl + (ah * bl + al * bh << 16) | 0;
1911
+ };
1912
+ }));
1913
+
1914
+ //#endregion
1915
+ //#region node_modules/.pnpm/lz4js@0.2.0/node_modules/lz4js/xxh32.js
1916
+ var require_xxh32 = /* @__PURE__ */ __commonJSMin(((exports) => {
1917
+ var util = require_util();
1918
+ var prime1 = 2654435761;
1919
+ var prime2 = 2246822519;
1920
+ var prime3 = 3266489917;
1921
+ var prime4 = 668265263;
1922
+ var prime5 = 374761393;
1923
+ function rotl32(x, r) {
1924
+ x = x | 0;
1925
+ r = r | 0;
1926
+ return x >>> (32 - r | 0) | x << r | 0;
1927
+ }
1928
+ function rotmul32(h, r, m) {
1929
+ h = h | 0;
1930
+ r = r | 0;
1931
+ m = m | 0;
1932
+ return util.imul(h >>> (32 - r | 0) | h << r, m) | 0;
1933
+ }
1934
+ function shiftxor32(h, s) {
1935
+ h = h | 0;
1936
+ s = s | 0;
1937
+ return h >>> s ^ h | 0;
1938
+ }
1939
+ function xxhapply(h, src, m0, s, m1) {
1940
+ return rotmul32(util.imul(src, m0) + h, s, m1);
1941
+ }
1942
+ function xxh1(h, src, index) {
1943
+ return rotmul32(h + util.imul(src[index], prime5), 11, prime1);
1944
+ }
1945
+ function xxh4(h, src, index) {
1946
+ return xxhapply(h, util.readU32(src, index), prime3, 17, prime4);
1947
+ }
1948
+ function xxh16(h, src, index) {
1949
+ return [
1950
+ xxhapply(h[0], util.readU32(src, index + 0), prime2, 13, prime1),
1951
+ xxhapply(h[1], util.readU32(src, index + 4), prime2, 13, prime1),
1952
+ xxhapply(h[2], util.readU32(src, index + 8), prime2, 13, prime1),
1953
+ xxhapply(h[3], util.readU32(src, index + 12), prime2, 13, prime1)
1954
+ ];
1955
+ }
1956
+ function xxh32(seed, src, index, len) {
1957
+ var h, l = len;
1958
+ if (len >= 16) {
1959
+ h = [
1960
+ seed + prime1 + prime2,
1961
+ seed + prime2,
1962
+ seed,
1963
+ seed - prime1
1964
+ ];
1965
+ while (len >= 16) {
1966
+ h = xxh16(h, src, index);
1967
+ index += 16;
1968
+ len -= 16;
1969
+ }
1970
+ h = rotl32(h[0], 1) + rotl32(h[1], 7) + rotl32(h[2], 12) + rotl32(h[3], 18) + l;
1971
+ } else h = seed + prime5 + len >>> 0;
1972
+ while (len >= 4) {
1973
+ h = xxh4(h, src, index);
1974
+ index += 4;
1975
+ len -= 4;
1976
+ }
1977
+ while (len > 0) {
1978
+ h = xxh1(h, src, index);
1979
+ index++;
1980
+ len--;
1981
+ }
1982
+ h = shiftxor32(util.imul(shiftxor32(util.imul(shiftxor32(h, 15), prime2), 13), prime3), 16);
1983
+ return h >>> 0;
1984
+ }
1985
+ exports.hash = xxh32;
1986
+ }));
1987
+
1988
+ //#endregion
1989
+ //#region node_modules/.pnpm/lz4js@0.2.0/node_modules/lz4js/lz4.js
1990
+ var require_lz4 = /* @__PURE__ */ __commonJSMin(((exports) => {
1991
+ var xxhash = require_xxh32();
1992
+ var util = require_util();
1993
+ var minMatch = 4;
1994
+ var minLength = 13;
1995
+ var searchLimit = 5;
1996
+ var skipTrigger = 6;
1997
+ var hashSize = 65536;
1998
+ var mlBits = 4;
1999
+ var mlMask = (1 << mlBits) - 1;
2000
+ var runMask = 15;
2001
+ var blockBuf = makeBuffer(5 << 20);
2002
+ var hashTable = makeHashTable();
2003
+ var magicNum = 407708164;
2004
+ var fdContentChksum = 4;
2005
+ var fdContentSize = 8;
2006
+ var fdBlockChksum = 16;
2007
+ var fdVersion = 64;
2008
+ var fdVersionMask = 192;
2009
+ var bsUncompressed = 2147483648;
2010
+ var bsDefault = 7;
2011
+ var bsShift = 4;
2012
+ var bsMask = 7;
2013
+ var bsMap = {
2014
+ 4: 65536,
2015
+ 5: 262144,
2016
+ 6: 1048576,
2017
+ 7: 4194304
2018
+ };
2019
+ function makeHashTable() {
2020
+ try {
2021
+ return new Uint32Array(hashSize);
2022
+ } catch (error) {
2023
+ var hashTable = new Array(hashSize);
2024
+ for (var i = 0; i < hashSize; i++) hashTable[i] = 0;
2025
+ return hashTable;
2026
+ }
2027
+ }
2028
+ function clearHashTable(table) {
2029
+ for (var i = 0; i < hashSize; i++) hashTable[i] = 0;
2030
+ }
2031
+ function makeBuffer(size) {
2032
+ try {
2033
+ return new Uint8Array(size);
2034
+ } catch (error) {
2035
+ var buf = new Array(size);
2036
+ for (var i = 0; i < size; i++) buf[i] = 0;
2037
+ return buf;
2038
+ }
2039
+ }
2040
+ function sliceArray(array, start, end) {
2041
+ if (Uint8Array.prototype.slice) return array.slice(start, end);
2042
+ else {
2043
+ var len = array.length;
2044
+ start = start | 0;
2045
+ start = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);
2046
+ end = end === void 0 ? len : end | 0;
2047
+ end = end < 0 ? Math.max(len + end, 0) : Math.min(end, len);
2048
+ var arraySlice = new Uint8Array(end - start);
2049
+ for (var i = start, n = 0; i < end;) arraySlice[n++] = array[i++];
2050
+ return arraySlice;
2051
+ }
2052
+ }
2053
+ exports.compressBound = function compressBound(n) {
2054
+ return n + n / 255 + 16 | 0;
2055
+ };
2056
+ exports.decompressBound = function decompressBound(src) {
2057
+ var sIndex = 0;
2058
+ if (util.readU32(src, sIndex) !== magicNum) throw new Error("invalid magic number");
2059
+ sIndex += 4;
2060
+ var descriptor = src[sIndex++];
2061
+ if ((descriptor & fdVersionMask) !== fdVersion) throw new Error("incompatible descriptor version " + (descriptor & fdVersionMask));
2062
+ var useBlockSum = (descriptor & fdBlockChksum) !== 0;
2063
+ var useContentSize = (descriptor & fdContentSize) !== 0;
2064
+ var bsIdx = src[sIndex++] >> bsShift & bsMask;
2065
+ if (bsMap[bsIdx] === void 0) throw new Error("invalid block size " + bsIdx);
2066
+ var maxBlockSize = bsMap[bsIdx];
2067
+ if (useContentSize) return util.readU64(src, sIndex);
2068
+ sIndex++;
2069
+ var maxSize = 0;
2070
+ while (true) {
2071
+ var blockSize = util.readU32(src, sIndex);
2072
+ sIndex += 4;
2073
+ if (blockSize & bsUncompressed) {
2074
+ blockSize &= ~bsUncompressed;
2075
+ maxSize += blockSize;
2076
+ } else maxSize += maxBlockSize;
2077
+ if (blockSize === 0) return maxSize;
2078
+ if (useBlockSum) sIndex += 4;
2079
+ sIndex += blockSize;
2080
+ }
2081
+ };
2082
+ exports.makeBuffer = makeBuffer;
2083
+ exports.decompressBlock = function decompressBlock(src, dst, sIndex, sLength, dIndex) {
2084
+ var mLength, mOffset, sEnd = sIndex + sLength, n, i;
2085
+ while (sIndex < sEnd) {
2086
+ var token = src[sIndex++];
2087
+ var literalCount = token >> 4;
2088
+ if (literalCount > 0) {
2089
+ if (literalCount === 15) while (true) {
2090
+ literalCount += src[sIndex];
2091
+ if (src[sIndex++] !== 255) break;
2092
+ }
2093
+ for (n = sIndex + literalCount; sIndex < n;) dst[dIndex++] = src[sIndex++];
2094
+ }
2095
+ if (sIndex >= sEnd) break;
2096
+ mLength = token & 15;
2097
+ mOffset = src[sIndex++] | src[sIndex++] << 8;
2098
+ if (mLength === 15) while (true) {
2099
+ mLength += src[sIndex];
2100
+ if (src[sIndex++] !== 255) break;
2101
+ }
2102
+ mLength += minMatch;
2103
+ for (i = dIndex - mOffset, n = i + mLength; i < n;) dst[dIndex++] = dst[i++] | 0;
2104
+ }
2105
+ return dIndex;
2106
+ };
2107
+ exports.compressBlock = function compressBlock(src, dst, sIndex, sLength, hashTable) {
2108
+ var mIndex, mAnchor, mLength, mOffset, mStep;
2109
+ var literalCount, dIndex = 0, sEnd = sLength + sIndex, n;
2110
+ mAnchor = sIndex;
2111
+ if (sLength >= minLength) {
2112
+ var searchMatchCount = (1 << skipTrigger) + 3;
2113
+ while (sIndex + minMatch < sEnd - searchLimit) {
2114
+ var seq = util.readU32(src, sIndex);
2115
+ var hash = util.hashU32(seq) >>> 0;
2116
+ hash = (hash >> 16 ^ hash) >>> 0 & 65535;
2117
+ mIndex = hashTable[hash] - 1;
2118
+ hashTable[hash] = sIndex + 1;
2119
+ if (mIndex < 0 || sIndex - mIndex >>> 16 > 0 || util.readU32(src, mIndex) !== seq) {
2120
+ mStep = searchMatchCount++ >> skipTrigger;
2121
+ sIndex += mStep;
2122
+ continue;
2123
+ }
2124
+ searchMatchCount = (1 << skipTrigger) + 3;
2125
+ literalCount = sIndex - mAnchor;
2126
+ mOffset = sIndex - mIndex;
2127
+ sIndex += minMatch;
2128
+ mIndex += minMatch;
2129
+ mLength = sIndex;
2130
+ while (sIndex < sEnd - searchLimit && src[sIndex] === src[mIndex]) {
2131
+ sIndex++;
2132
+ mIndex++;
2133
+ }
2134
+ mLength = sIndex - mLength;
2135
+ var token = mLength < mlMask ? mLength : mlMask;
2136
+ if (literalCount >= runMask) {
2137
+ dst[dIndex++] = (runMask << mlBits) + token;
2138
+ for (n = literalCount - runMask; n >= 255; n -= 255) dst[dIndex++] = 255;
2139
+ dst[dIndex++] = n;
2140
+ } else dst[dIndex++] = (literalCount << mlBits) + token;
2141
+ for (var i = 0; i < literalCount; i++) dst[dIndex++] = src[mAnchor + i];
2142
+ dst[dIndex++] = mOffset;
2143
+ dst[dIndex++] = mOffset >> 8;
2144
+ if (mLength >= mlMask) {
2145
+ for (n = mLength - mlMask; n >= 255; n -= 255) dst[dIndex++] = 255;
2146
+ dst[dIndex++] = n;
2147
+ }
2148
+ mAnchor = sIndex;
2149
+ }
2150
+ }
2151
+ if (mAnchor === 0) return 0;
2152
+ literalCount = sEnd - mAnchor;
2153
+ if (literalCount >= runMask) {
2154
+ dst[dIndex++] = runMask << mlBits;
2155
+ for (n = literalCount - runMask; n >= 255; n -= 255) dst[dIndex++] = 255;
2156
+ dst[dIndex++] = n;
2157
+ } else dst[dIndex++] = literalCount << mlBits;
2158
+ sIndex = mAnchor;
2159
+ while (sIndex < sEnd) dst[dIndex++] = src[sIndex++];
2160
+ return dIndex;
2161
+ };
2162
+ exports.decompressFrame = function decompressFrame(src, dst) {
2163
+ var useBlockSum, useContentSum, useContentSize, descriptor;
2164
+ var sIndex = 0;
2165
+ var dIndex = 0;
2166
+ if (util.readU32(src, sIndex) !== magicNum) throw new Error("invalid magic number");
2167
+ sIndex += 4;
2168
+ descriptor = src[sIndex++];
2169
+ if ((descriptor & fdVersionMask) !== fdVersion) throw new Error("incompatible descriptor version");
2170
+ useBlockSum = (descriptor & fdBlockChksum) !== 0;
2171
+ useContentSum = (descriptor & fdContentChksum) !== 0;
2172
+ useContentSize = (descriptor & fdContentSize) !== 0;
2173
+ if (bsMap[src[sIndex++] >> bsShift & bsMask] === void 0) throw new Error("invalid block size");
2174
+ if (useContentSize) sIndex += 8;
2175
+ sIndex++;
2176
+ while (true) {
2177
+ var compSize = util.readU32(src, sIndex);
2178
+ sIndex += 4;
2179
+ if (compSize === 0) break;
2180
+ if (useBlockSum) sIndex += 4;
2181
+ if ((compSize & bsUncompressed) !== 0) {
2182
+ compSize &= ~bsUncompressed;
2183
+ for (var j = 0; j < compSize; j++) dst[dIndex++] = src[sIndex++];
2184
+ } else {
2185
+ dIndex = exports.decompressBlock(src, dst, sIndex, compSize, dIndex);
2186
+ sIndex += compSize;
2187
+ }
2188
+ }
2189
+ if (useContentSum) sIndex += 4;
2190
+ return dIndex;
2191
+ };
2192
+ exports.compressFrame = function compressFrame(src, dst) {
2193
+ var dIndex = 0;
2194
+ util.writeU32(dst, dIndex, magicNum);
2195
+ dIndex += 4;
2196
+ dst[dIndex++] = fdVersion;
2197
+ dst[dIndex++] = bsDefault << bsShift;
2198
+ dst[dIndex] = xxhash.hash(0, dst, 4, dIndex - 4) >> 8;
2199
+ dIndex++;
2200
+ var maxBlockSize = bsMap[bsDefault];
2201
+ var remaining = src.length;
2202
+ var sIndex = 0;
2203
+ clearHashTable(hashTable);
2204
+ while (remaining > 0) {
2205
+ var compSize = 0;
2206
+ var blockSize = remaining > maxBlockSize ? maxBlockSize : remaining;
2207
+ compSize = exports.compressBlock(src, blockBuf, sIndex, blockSize, hashTable);
2208
+ if (compSize > blockSize || compSize === 0) {
2209
+ util.writeU32(dst, dIndex, 2147483648 | blockSize);
2210
+ dIndex += 4;
2211
+ for (var z = sIndex + blockSize; sIndex < z;) dst[dIndex++] = src[sIndex++];
2212
+ remaining -= blockSize;
2213
+ } else {
2214
+ util.writeU32(dst, dIndex, compSize);
2215
+ dIndex += 4;
2216
+ for (var j = 0; j < compSize;) dst[dIndex++] = blockBuf[j++];
2217
+ sIndex += blockSize;
2218
+ remaining -= blockSize;
2219
+ }
2220
+ }
2221
+ util.writeU32(dst, dIndex, 0);
2222
+ dIndex += 4;
2223
+ return dIndex;
2224
+ };
2225
+ exports.decompress = function decompress(src, maxSize) {
2226
+ var dst, size;
2227
+ if (maxSize === void 0) maxSize = exports.decompressBound(src);
2228
+ dst = exports.makeBuffer(maxSize);
2229
+ size = exports.decompressFrame(src, dst);
2230
+ if (size !== maxSize) dst = sliceArray(dst, 0, size);
2231
+ return dst;
2232
+ };
2233
+ exports.compress = function compress(src, maxSize) {
2234
+ var dst, size;
2235
+ if (maxSize === void 0) maxSize = exports.compressBound(src.length);
2236
+ dst = exports.makeBuffer(maxSize);
2237
+ size = exports.compressFrame(src, dst);
2238
+ if (size !== maxSize) dst = sliceArray(dst, 0, size);
2239
+ return dst;
2240
+ };
2241
+ }));
2242
+
2243
+ //#endregion
2244
+ //#region src/compression/lz4.ts
2245
+ var import_lz4 = require_lz4();
2246
+ /**
2247
+ * Default compression options
2248
+ */
2249
+ const DEFAULT_COMPRESSION_OPTIONS = {
2250
+ threshold: 1024,
2251
+ acceleration: 1,
2252
+ level: 0
2253
+ };
2254
+ /**
2255
+ * Compress data using LZ4
2256
+ * @param data - Data to compress
2257
+ * @param options - Compression options
2258
+ * @returns Compressed data, or null if compression failed or not beneficial
2259
+ */
2260
+ function compress(data, options = {}) {
2261
+ const opts = {
2262
+ ...DEFAULT_COMPRESSION_OPTIONS,
2263
+ ...options
2264
+ };
2265
+ if (data.length < opts.threshold) return null;
2266
+ try {
2267
+ const compressed = (0, import_lz4.compress)(Array.from(data));
2268
+ if (compressed.length >= data.length) return null;
2269
+ return new Uint8Array(compressed);
2270
+ } catch (_error) {
2271
+ return null;
2272
+ }
2273
+ }
2274
+ /**
2275
+ * Decompress LZ4 compressed data
2276
+ * @param data - Compressed data
2277
+ * @param originalSize - Original uncompressed size
2278
+ * @returns Decompressed data, or null if decompression failed
2279
+ */
2280
+ function decompress(data, originalSize) {
2281
+ try {
2282
+ const decompressed = (0, import_lz4.decompress)(Array.from(data), originalSize);
2283
+ return new Uint8Array(decompressed);
2284
+ } catch (_error) {
2285
+ return null;
2286
+ }
2287
+ }
2288
+ /**
2289
+ * Alias for decompress - same function, different name for API compatibility
2290
+ */
2291
+ const uncompress = decompress;
2292
+
1813
2293
  //#endregion
1814
2294
  //#region src/rpc/tcp-transport.ts
1815
2295
  /**
@@ -1818,11 +2298,20 @@ var WebSocketTransport = class WebSocketTransport {
1818
2298
  * Implements RpcTransport over raw TCP socket for C++ interop testing.
1819
2299
  * This allows direct communication with the official Cap'n Proto C++ implementation.
1820
2300
  */
2301
+ const FRAME_MAGIC = 1280980035;
2302
+ const ALGORITHM_LZ4 = 1;
2303
+ const CAPABILITY_VERSION = 1;
2304
+ const DEFAULT_COMPRESSION_THRESHOLD = 256;
1821
2305
  /**
1822
2306
  * TCP Transport for Cap'n Proto RPC
1823
2307
  *
1824
2308
  * Uses length-prefixed binary message framing compatible with Cap'n Proto C++ implementation.
1825
2309
  * Format: [4 bytes: message length (little-endian)] [N bytes: message data]
2310
+ *
2311
+ * With compression support:
2312
+ * - Connection establishment includes capability negotiation
2313
+ * - Messages larger than threshold are automatically compressed
2314
+ * - Frame header indicates compression algorithm used
1826
2315
  */
1827
2316
  var TcpTransport = class TcpTransport {
1828
2317
  socket = null;
@@ -1832,12 +2321,35 @@ var TcpTransport = class TcpTransport {
1832
2321
  pendingBuffer = Buffer.alloc(0);
1833
2322
  pendingLength = 0;
1834
2323
  hasPendingLength = false;
2324
+ compressionConfig;
2325
+ localSupportsLz4 = false;
2326
+ remoteSupportsLz4 = false;
2327
+ compressionEnabled = false;
2328
+ compressionState;
2329
+ negotiationComplete = false;
1835
2330
  onClose;
1836
2331
  onError;
1837
2332
  constructor(host, port, options = {}) {
1838
2333
  this.host = host;
1839
2334
  this.port = port;
1840
2335
  this.options = options;
2336
+ this.compressionConfig = {
2337
+ enabled: options.compression?.enabled ?? true,
2338
+ algorithm: options.compression?.algorithm ?? "lz4",
2339
+ thresholdBytes: options.compression?.thresholdBytes ?? DEFAULT_COMPRESSION_THRESHOLD,
2340
+ level: options.compression?.level ?? 1
2341
+ };
2342
+ this.localSupportsLz4 = this.compressionConfig.enabled && this.compressionConfig.algorithm === "lz4";
2343
+ this.compressionState = {
2344
+ enabled: false,
2345
+ algorithm: "none",
2346
+ bytesSent: 0,
2347
+ bytesReceived: 0,
2348
+ uncompressedBytesSent: 0,
2349
+ uncompressedBytesReceived: 0,
2350
+ messagesCompressed: 0,
2351
+ messagesDecompressed: 0
2352
+ };
1841
2353
  }
1842
2354
  static async connect(host, port, options) {
1843
2355
  const transport = new TcpTransport(host, port, options);
@@ -1852,6 +2364,9 @@ var TcpTransport = class TcpTransport {
1852
2364
  get connected() {
1853
2365
  return this._connected && this.socket?.readyState === "open";
1854
2366
  }
2367
+ getCompressionState() {
2368
+ return { ...this.compressionState };
2369
+ }
1855
2370
  connect() {
1856
2371
  return new Promise((resolve, reject) => {
1857
2372
  const timeout = setTimeout(() => {
@@ -1859,10 +2374,15 @@ var TcpTransport = class TcpTransport {
1859
2374
  reject(/* @__PURE__ */ new Error("Connection timeout"));
1860
2375
  }, this.options.connectTimeoutMs ?? 1e4);
1861
2376
  this.socket = new node_net.Socket();
1862
- this.socket.on("connect", () => {
2377
+ this.socket.on("connect", async () => {
1863
2378
  clearTimeout(timeout);
1864
2379
  this._connected = true;
1865
- resolve();
2380
+ try {
2381
+ await this.performCapabilityNegotiation();
2382
+ resolve();
2383
+ } catch (err) {
2384
+ reject(err instanceof Error ? err : new Error(String(err)));
2385
+ }
1866
2386
  });
1867
2387
  this.socket.on("data", (data) => {
1868
2388
  this.handleData(data);
@@ -1904,6 +2424,85 @@ var TcpTransport = class TcpTransport {
1904
2424
  socket.on("end", () => {
1905
2425
  this._connected = false;
1906
2426
  });
2427
+ if (this._connected) this.performCapabilityNegotiation().catch((err) => {
2428
+ this.onError?.(err instanceof Error ? err : new Error(String(err)));
2429
+ });
2430
+ }
2431
+ /**
2432
+ * Perform capability negotiation with the remote peer
2433
+ */
2434
+ async performCapabilityNegotiation() {
2435
+ if (!this.socket) throw new Error("Socket not connected");
2436
+ if (this.host === "" && this.port === 0) {
2437
+ this.remoteSupportsLz4 = (await this.readCapabilityNegotiation()).supportsLz4;
2438
+ await this.sendCapabilityNegotiation();
2439
+ } else {
2440
+ await this.sendCapabilityNegotiation();
2441
+ this.remoteSupportsLz4 = (await this.readCapabilityNegotiation()).supportsLz4;
2442
+ }
2443
+ this.compressionEnabled = this.localSupportsLz4 && this.remoteSupportsLz4;
2444
+ this.compressionState.enabled = this.compressionEnabled;
2445
+ this.compressionState.algorithm = this.compressionEnabled ? "lz4" : "none";
2446
+ this.negotiationComplete = true;
2447
+ }
2448
+ /**
2449
+ * Send capability negotiation message
2450
+ */
2451
+ async sendCapabilityNegotiation() {
2452
+ const caps = {
2453
+ type: "capabilities",
2454
+ supportsLz4: this.localSupportsLz4,
2455
+ version: CAPABILITY_VERSION
2456
+ };
2457
+ const data = Buffer.from(JSON.stringify(caps), "utf-8");
2458
+ const frame = Buffer.allocUnsafe(4 + data.length);
2459
+ frame.writeUInt32LE(data.length, 0);
2460
+ frame.set(data, 4);
2461
+ return new Promise((resolve, reject) => {
2462
+ this.socket.write(frame, (err) => {
2463
+ if (err) reject(err);
2464
+ else resolve();
2465
+ });
2466
+ });
2467
+ }
2468
+ /**
2469
+ * Read capability negotiation message
2470
+ */
2471
+ async readCapabilityNegotiation() {
2472
+ return new Promise((resolve, reject) => {
2473
+ const timeout = setTimeout(() => {
2474
+ reject(/* @__PURE__ */ new Error("Capability negotiation timeout"));
2475
+ }, 5e3);
2476
+ const checkBuffer = () => {
2477
+ if (this.pendingBuffer.length < 4) {
2478
+ this.socket.listenerCount("data");
2479
+ const dataHandler = () => {
2480
+ this.socket.off("data", dataHandler);
2481
+ checkBuffer();
2482
+ };
2483
+ this.socket.once("data", dataHandler);
2484
+ return;
2485
+ }
2486
+ const length = this.pendingBuffer.readUInt32LE(0);
2487
+ if (this.pendingBuffer.length < 4 + length) {
2488
+ const dataHandler = () => {
2489
+ this.socket.off("data", dataHandler);
2490
+ checkBuffer();
2491
+ };
2492
+ this.socket.once("data", dataHandler);
2493
+ return;
2494
+ }
2495
+ clearTimeout(timeout);
2496
+ const capsData = this.pendingBuffer.subarray(4, 4 + length);
2497
+ this.pendingBuffer = this.pendingBuffer.subarray(4 + length);
2498
+ try {
2499
+ resolve(JSON.parse(capsData.toString("utf-8")));
2500
+ } catch (err) {
2501
+ reject(/* @__PURE__ */ new Error(`Failed to parse capability negotiation: ${err}`));
2502
+ }
2503
+ };
2504
+ checkBuffer();
2505
+ });
1907
2506
  }
1908
2507
  handleData(data) {
1909
2508
  this.pendingBuffer = Buffer.concat([this.pendingBuffer, data]);
@@ -1919,14 +2518,50 @@ var TcpTransport = class TcpTransport {
1919
2518
  }
1920
2519
  const totalLength = 4 + this.pendingLength;
1921
2520
  if (this.pendingBuffer.length < totalLength) break;
1922
- const messageData = this.pendingBuffer.subarray(4, totalLength);
1923
- this.pendingBuffer = this.pendingBuffer.subarray(totalLength);
1924
- this.hasPendingLength = false;
1925
- try {
1926
- const message = deserializeRpcMessage(new Uint8Array(messageData));
1927
- this.handleRpcMessage(message);
1928
- } catch (err) {
1929
- this.onError?.(err instanceof Error ? err : new Error(String(err)));
2521
+ if (this.pendingBuffer.readUInt32LE(4) === FRAME_MAGIC && this.negotiationComplete) {
2522
+ if (this.pendingLength < 16) {
2523
+ this.onError?.(/* @__PURE__ */ new Error("Invalid compressed frame"));
2524
+ this.close(/* @__PURE__ */ new Error("Invalid compressed frame"));
2525
+ return;
2526
+ }
2527
+ const headerOffset = 4;
2528
+ const originalSize = this.pendingBuffer.readUInt32LE(headerOffset + 4);
2529
+ const compressedSize = this.pendingBuffer.readUInt32LE(headerOffset + 8);
2530
+ const algorithm = this.pendingBuffer.readUInt32LE(headerOffset + 12);
2531
+ const frameTotalLength = 20 + compressedSize;
2532
+ if (this.pendingBuffer.length < frameTotalLength) break;
2533
+ const compressedData = this.pendingBuffer.subarray(headerOffset + 16, frameTotalLength);
2534
+ this.pendingBuffer = this.pendingBuffer.subarray(frameTotalLength);
2535
+ this.hasPendingLength = false;
2536
+ try {
2537
+ let messageData;
2538
+ if (algorithm === ALGORITHM_LZ4) {
2539
+ const decompressed = decompress(compressedData, originalSize);
2540
+ if (!decompressed) throw new Error("LZ4 decompression failed");
2541
+ messageData = decompressed;
2542
+ this.compressionState.messagesDecompressed++;
2543
+ this.compressionState.bytesReceived += compressedData.length;
2544
+ this.compressionState.uncompressedBytesReceived += messageData.length;
2545
+ } else {
2546
+ messageData = new Uint8Array(compressedData);
2547
+ this.compressionState.bytesReceived += compressedData.length;
2548
+ this.compressionState.uncompressedBytesReceived += compressedData.length;
2549
+ }
2550
+ const message = deserializeRpcMessage(messageData);
2551
+ this.handleRpcMessage(message);
2552
+ } catch (err) {
2553
+ this.onError?.(err instanceof Error ? err : new Error(String(err)));
2554
+ }
2555
+ } else {
2556
+ const messageData = this.pendingBuffer.subarray(4, totalLength);
2557
+ this.pendingBuffer = this.pendingBuffer.subarray(totalLength);
2558
+ this.hasPendingLength = false;
2559
+ try {
2560
+ const message = deserializeRpcMessage(new Uint8Array(messageData));
2561
+ this.handleRpcMessage(message);
2562
+ } catch (err) {
2563
+ this.onError?.(err instanceof Error ? err : new Error(String(err)));
2564
+ }
1930
2565
  }
1931
2566
  }
1932
2567
  }
@@ -1938,10 +2573,31 @@ var TcpTransport = class TcpTransport {
1938
2573
  }
1939
2574
  async send(message) {
1940
2575
  if (!this.socket || this.socket.readyState !== "open") throw new Error("Socket not connected");
2576
+ if (!this.negotiationComplete) throw new Error("Capability negotiation not complete");
1941
2577
  const data = serializeRpcMessage(message);
1942
- const frame = Buffer.allocUnsafe(4 + data.length);
1943
- frame.writeUInt32LE(data.length, 0);
1944
- frame.set(data, 4);
2578
+ const shouldCompress = this.compressionEnabled && data.length >= this.compressionConfig.thresholdBytes;
2579
+ let frame;
2580
+ if (shouldCompress) {
2581
+ const compressed = compress(data, { threshold: 0 });
2582
+ if (!compressed) throw new Error("Compression failed");
2583
+ const frameLength = 16 + compressed.length;
2584
+ frame = Buffer.allocUnsafe(4 + frameLength);
2585
+ frame.writeUInt32LE(frameLength, 0);
2586
+ frame.writeUInt32LE(FRAME_MAGIC, 4);
2587
+ frame.writeUInt32LE(data.length, 8);
2588
+ frame.writeUInt32LE(compressed.length, 12);
2589
+ frame.writeUInt32LE(ALGORITHM_LZ4, 16);
2590
+ frame.set(compressed, 20);
2591
+ this.compressionState.bytesSent += compressed.length;
2592
+ this.compressionState.uncompressedBytesSent += data.length;
2593
+ this.compressionState.messagesCompressed++;
2594
+ } else {
2595
+ frame = Buffer.allocUnsafe(4 + data.length);
2596
+ frame.writeUInt32LE(data.length, 0);
2597
+ frame.set(data, 4);
2598
+ this.compressionState.bytesSent += data.length;
2599
+ this.compressionState.uncompressedBytesSent += data.length;
2600
+ }
1945
2601
  return new Promise((resolve, reject) => {
1946
2602
  this.socket.write(frame, (err) => {
1947
2603
  if (err) reject(err);
@@ -2009,6 +2665,18 @@ var EzRpcTransport = class EzRpcTransport {
2009
2665
  get connected() {
2010
2666
  return this._connected && this.socket !== null && !this.socket.destroyed;
2011
2667
  }
2668
+ getCompressionState() {
2669
+ return {
2670
+ enabled: false,
2671
+ algorithm: "none",
2672
+ bytesSent: 0,
2673
+ bytesReceived: 0,
2674
+ uncompressedBytesSent: 0,
2675
+ uncompressedBytesReceived: 0,
2676
+ messagesCompressed: 0,
2677
+ messagesDecompressed: 0
2678
+ };
2679
+ }
2012
2680
  doConnect() {
2013
2681
  return new Promise((resolve, reject) => {
2014
2682
  const timeout = setTimeout(() => {
@@ -9783,6 +10451,131 @@ var import_sender = /* @__PURE__ */ __toESM(require_sender(), 1);
9783
10451
  var import_websocket = /* @__PURE__ */ __toESM(require_websocket(), 1);
9784
10452
  var import_websocket_server = /* @__PURE__ */ __toESM(require_websocket_server(), 1);
9785
10453
 
10454
+ //#endregion
10455
+ //#region src/compression/frame.ts
10456
+ /**
10457
+ * LZ4 Frame Format Module
10458
+ *
10459
+ * Frame format: Magic(4) + Flags(1) + Length(4) + Payload
10460
+ * - Magic: 0x4C5A3401 (LZ4\0\x01)
10461
+ * - Flags: compression options and metadata
10462
+ * - Length: uncompressed data length (4 bytes, little-endian)
10463
+ * - Payload: compressed or uncompressed data
10464
+ */
10465
+ /**
10466
+ * Frame format constants
10467
+ */
10468
+ const LZ4_FRAME_MAGIC = 1280979969;
10469
+ const LZ4_FRAME_HEADER_SIZE = 9;
10470
+ /**
10471
+ * Frame flags
10472
+ */
10473
+ let FrameFlags = /* @__PURE__ */ function(FrameFlags) {
10474
+ /** Data is compressed */
10475
+ FrameFlags[FrameFlags["COMPRESSED"] = 1] = "COMPRESSED";
10476
+ /** Reserved for future use */
10477
+ FrameFlags[FrameFlags["RESERVED_1"] = 2] = "RESERVED_1";
10478
+ /** Reserved for future use */
10479
+ FrameFlags[FrameFlags["RESERVED_2"] = 4] = "RESERVED_2";
10480
+ /** Reserved for future use */
10481
+ FrameFlags[FrameFlags["RESERVED_3"] = 8] = "RESERVED_3";
10482
+ /** Reserved for future use */
10483
+ FrameFlags[FrameFlags["RESERVED_4"] = 16] = "RESERVED_4";
10484
+ /** Reserved for future use */
10485
+ FrameFlags[FrameFlags["RESERVED_5"] = 32] = "RESERVED_5";
10486
+ /** Reserved for future use */
10487
+ FrameFlags[FrameFlags["RESERVED_6"] = 64] = "RESERVED_6";
10488
+ /** Reserved for future use */
10489
+ FrameFlags[FrameFlags["RESERVED_7"] = 128] = "RESERVED_7";
10490
+ return FrameFlags;
10491
+ }({});
10492
+ /**
10493
+ * Check if data has LZ4 frame magic header
10494
+ */
10495
+ function hasFrameMagic(data) {
10496
+ if (data.length < 4) return false;
10497
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true) === LZ4_FRAME_MAGIC;
10498
+ }
10499
+ /**
10500
+ * Parse frame header from data
10501
+ * @returns FrameHeader if valid, null otherwise
10502
+ */
10503
+ function parseFrameHeader(data) {
10504
+ if (data.length < LZ4_FRAME_HEADER_SIZE) return null;
10505
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
10506
+ const magic = view.getUint32(0, true);
10507
+ if (magic !== LZ4_FRAME_MAGIC) return null;
10508
+ return {
10509
+ magic,
10510
+ flags: view.getUint8(4),
10511
+ length: view.getUint32(5, true)
10512
+ };
10513
+ }
10514
+ /**
10515
+ * Decompress framed data
10516
+ * @param data - Framed data (with or without frame header)
10517
+ * @param decompressFn - Decompression function for compressed payloads
10518
+ * @returns Decompressed data, or null if invalid
10519
+ */
10520
+ function decompressFrame(data, decompressFn) {
10521
+ const header = parseFrameHeader(data);
10522
+ if (!header) return data;
10523
+ const payloadOffset = LZ4_FRAME_HEADER_SIZE;
10524
+ if (data.length - payloadOffset < 0) return null;
10525
+ const payload = data.subarray(payloadOffset);
10526
+ if (header.flags & FrameFlags.COMPRESSED) return decompressFn(payload, header.length);
10527
+ return payload;
10528
+ }
10529
+
10530
+ //#endregion
10531
+ //#region src/compression/index.ts
10532
+ /**
10533
+ * Create default compression config
10534
+ */
10535
+ function createCompressionConfig(config) {
10536
+ return {
10537
+ enabled: false,
10538
+ algorithm: "lz4",
10539
+ threshold: 1024,
10540
+ level: 0,
10541
+ highCompression: false,
10542
+ ...config
10543
+ };
10544
+ }
10545
+ /**
10546
+ * Create empty compression stats
10547
+ */
10548
+ function createCompressionStats() {
10549
+ return {
10550
+ messagesCompressed: 0,
10551
+ messagesDecompressed: 0,
10552
+ bytesOriginal: 0,
10553
+ bytesCompressed: 0,
10554
+ compressionRatio: 1,
10555
+ savingsPercent: 0
10556
+ };
10557
+ }
10558
+ /**
10559
+ * Check if data is a compression frame (has LZ4 magic)
10560
+ * Works with both Buffer and Uint8Array
10561
+ */
10562
+ function isCompressionFrame(data) {
10563
+ return hasFrameMagic(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
10564
+ }
10565
+ /**
10566
+ * Try to decompress, return original if not compressed or decompression fails
10567
+ * Works with both Buffer and Uint8Array
10568
+ * Assumes data has LZ4 frame header (created by compressFrame)
10569
+ */
10570
+ function tryDecompress(data) {
10571
+ const uint8Data = data instanceof Buffer ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : data;
10572
+ return decompressFrame(uint8Data, uncompress) ?? uint8Data;
10573
+ }
10574
+ /**
10575
+ * Default compression configuration
10576
+ */
10577
+ const DEFAULT_COMPRESSION_CONFIG = createCompressionConfig();
10578
+
9786
10579
  //#endregion
9787
10580
  //#region src/proxy/websocket-proxy.ts
9788
10581
  /**
@@ -9791,6 +10584,27 @@ var import_websocket_server = /* @__PURE__ */ __toESM(require_websocket_server()
9791
10584
  * Allows browsers to connect to native Cap'n Proto services (C++, etc.)
9792
10585
  * via WebSocket. Handles the protocol bridging between WebSocket (browser)
9793
10586
  * and raw TCP (Cap'n Proto services).
10587
+ *
10588
+ * Features:
10589
+ * - Independent compression configuration for WebSocket and TCP sides
10590
+ * - Automatic compression/decompression bridging
10591
+ * - Support for different compression settings on each side
10592
+ * - Backward compatibility with non-compressed peers
10593
+ */
10594
+ /**
10595
+ * Convert Buffer to Uint8Array without copying if possible
10596
+ */
10597
+ function bufferToUint8Array(buffer) {
10598
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
10599
+ }
10600
+ /**
10601
+ * Convert Uint8Array to Buffer without copying if possible
10602
+ */
10603
+ function uint8ArrayToBuffer(arr) {
10604
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
10605
+ }
10606
+ /**
10607
+ * Represents a single proxy connection between WebSocket client and TCP server
9794
10608
  */
9795
10609
  var ProxyConnection = class extends node_events.EventEmitter {
9796
10610
  ws;
@@ -9798,16 +10612,22 @@ var ProxyConnection = class extends node_events.EventEmitter {
9798
10612
  stats;
9799
10613
  options;
9800
10614
  closed = false;
10615
+ wsCompressionConfig;
10616
+ tcpCompressionConfig;
9801
10617
  constructor(ws, options) {
9802
10618
  super();
9803
10619
  this.ws = ws;
9804
10620
  this.options = options;
10621
+ this.wsCompressionConfig = createCompressionConfig(options.compression?.ws);
10622
+ this.tcpCompressionConfig = createCompressionConfig(options.compression?.tcp);
9805
10623
  this.stats = {
9806
10624
  wsMessagesIn: 0,
9807
10625
  wsMessagesOut: 0,
9808
10626
  tcpBytesIn: 0,
9809
10627
  tcpBytesOut: 0,
9810
- connectedAt: /* @__PURE__ */ new Date()
10628
+ connectedAt: /* @__PURE__ */ new Date(),
10629
+ wsCompression: createCompressionStats(),
10630
+ tcpCompression: createCompressionStats()
9811
10631
  };
9812
10632
  this.setupWebSocket();
9813
10633
  }
@@ -9835,6 +10655,7 @@ var ProxyConnection = class extends node_events.EventEmitter {
9835
10655
  });
9836
10656
  this.tcpSocket.on("connect", () => {
9837
10657
  this.log("Connected to target TCP service");
10658
+ this.log(`Compression - WS: ${this.wsCompressionConfig.enabled ? "enabled" : "disabled"}, TCP: ${this.tcpCompressionConfig.enabled ? "enabled" : "disabled"}`);
9838
10659
  this.emit("connected");
9839
10660
  });
9840
10661
  this.tcpSocket.on("data", (data) => {
@@ -9854,29 +10675,78 @@ var ProxyConnection = class extends node_events.EventEmitter {
9854
10675
  this.close();
9855
10676
  });
9856
10677
  }
10678
+ /**
10679
+ * Handle incoming message from WebSocket client
10680
+ * - Decompress if compressed (from browser)
10681
+ * - Re-compress for TCP side if TCP compression is enabled
10682
+ * - Forward to TCP server
10683
+ */
9857
10684
  handleWebSocketMessage(data) {
9858
10685
  if (this.closed) return;
9859
10686
  const buffer = Buffer.isBuffer(data) ? data : Array.isArray(data) ? Buffer.concat(data) : Buffer.from(data);
9860
10687
  const maxSize = this.options.maxMessageSize ?? 16 * 1024 * 1024;
9861
- if (buffer.length > maxSize) {
9862
- this.log(`Message too large: ${buffer.length} bytes`);
10688
+ const uint8Data = bufferToUint8Array(buffer);
10689
+ const frameInfo = parseFrameHeader(uint8Data);
10690
+ const uncompressedSize = frameInfo ? frameInfo.length : buffer.length;
10691
+ if (uncompressedSize > maxSize) {
10692
+ this.log(`Message too large: ${uncompressedSize} bytes (max: ${maxSize})`);
9863
10693
  this.close();
9864
10694
  return;
9865
10695
  }
9866
10696
  this.stats.wsMessagesIn++;
9867
- this.stats.tcpBytesOut += buffer.length;
10697
+ let decompressedData;
10698
+ if (hasFrameMagic(uint8Data)) {
10699
+ const decompressed = decompressFrame(uint8Data, uncompress);
10700
+ if (decompressed !== null) {
10701
+ decompressedData = decompressed;
10702
+ this.log(`Decompressed WS message: ${buffer.length} -> ${decompressedData.length} bytes`);
10703
+ } else decompressedData = uint8Data;
10704
+ } else decompressedData = uint8Data;
10705
+ let dataToSend = decompressedData;
10706
+ if (this.tcpCompressionConfig.enabled) {
10707
+ const compressed = compress(decompressedData, { threshold: this.tcpCompressionConfig.threshold });
10708
+ if (compressed !== null) {
10709
+ dataToSend = compressed;
10710
+ this.log(`Compressed for TCP: ${decompressedData.length} -> ${dataToSend.length} bytes`);
10711
+ }
10712
+ }
10713
+ this.stats.tcpBytesOut += uint8ArrayToBuffer(dataToSend).length;
9868
10714
  if (this.tcpSocket?.writable) {
9869
- this.tcpSocket.write(buffer);
9870
- this.log(`Forwarded ${buffer.length} bytes to TCP`);
10715
+ const bufferToSend = uint8ArrayToBuffer(dataToSend);
10716
+ this.tcpSocket.write(bufferToSend);
10717
+ this.log(`Forwarded ${dataToSend.length} bytes to TCP (original: ${decompressedData.length})`);
9871
10718
  }
9872
10719
  }
10720
+ /**
10721
+ * Handle incoming data from TCP server
10722
+ * - Decompress if compressed (from native Cap'n Proto service)
10723
+ * - Re-compress for WebSocket side if WS compression is enabled
10724
+ * - Forward to WebSocket client
10725
+ */
9873
10726
  handleTcpData(data) {
9874
10727
  if (this.closed) return;
9875
10728
  this.stats.tcpBytesIn += data.length;
10729
+ const uint8Data = bufferToUint8Array(data);
10730
+ let decompressedData;
10731
+ if (hasFrameMagic(uint8Data)) {
10732
+ const decompressed = decompressFrame(uint8Data, uncompress);
10733
+ if (decompressed !== null) {
10734
+ decompressedData = decompressed;
10735
+ this.log(`Decompressed TCP message: ${data.length} -> ${decompressedData.length} bytes`);
10736
+ } else decompressedData = uint8Data;
10737
+ } else decompressedData = uint8Data;
10738
+ let dataToSend = decompressedData;
10739
+ if (this.wsCompressionConfig.enabled) {
10740
+ const compressed = compress(decompressedData, { threshold: this.wsCompressionConfig.threshold });
10741
+ if (compressed !== null) {
10742
+ dataToSend = compressed;
10743
+ this.log(`Compressed for WS: ${decompressedData.length} -> ${dataToSend.length} bytes`);
10744
+ }
10745
+ }
9876
10746
  this.stats.wsMessagesOut++;
9877
10747
  if (this.ws.readyState === import_websocket.default.OPEN) {
9878
- this.ws.send(data);
9879
- this.log(`Forwarded ${data.length} bytes to WebSocket`);
10748
+ this.ws.send(dataToSend);
10749
+ this.log(`Forwarded ${dataToSend.length} bytes to WebSocket (original: ${decompressedData.length})`);
9880
10750
  }
9881
10751
  }
9882
10752
  log(...args) {
@@ -9893,6 +10763,10 @@ var ProxyConnection = class extends node_events.EventEmitter {
9893
10763
  this.emit("closed");
9894
10764
  }
9895
10765
  };
10766
+ /**
10767
+ * WebSocket-to-TCP Proxy server
10768
+ * Manages multiple proxy connections
10769
+ */
9896
10770
  var CapnpWebSocketProxy = class extends node_events.EventEmitter {
9897
10771
  wss;
9898
10772
  connections = /* @__PURE__ */ new Map();
@@ -9950,6 +10824,9 @@ if (require("url").pathToFileURL(__filename).href === `file://${process.argv[1]}
9950
10824
  let targetHost = "localhost";
9951
10825
  let targetPort = 8081;
9952
10826
  let debug = false;
10827
+ let wsCompression = false;
10828
+ let tcpCompression = false;
10829
+ let compressionThreshold = 1024;
9953
10830
  for (let i = 0; i < args.length; i++) switch (args[i]) {
9954
10831
  case "--ws-port":
9955
10832
  case "-p":
@@ -9966,6 +10843,17 @@ if (require("url").pathToFileURL(__filename).href === `file://${process.argv[1]}
9966
10843
  case "-d":
9967
10844
  debug = true;
9968
10845
  break;
10846
+ case "--ws-compression":
10847
+ case "-w":
10848
+ wsCompression = true;
10849
+ break;
10850
+ case "--tcp-compression":
10851
+ case "-c":
10852
+ tcpCompression = true;
10853
+ break;
10854
+ case "--compression-threshold":
10855
+ compressionThreshold = Number.parseInt(args[++i], 10);
10856
+ break;
9969
10857
  case "--help":
9970
10858
  case "-h":
9971
10859
  console.log(`
@@ -9974,13 +10862,25 @@ Cap'n Proto WebSocket-to-TCP Proxy
9974
10862
  Usage: npx @naeemo/capnp proxy [options]
9975
10863
 
9976
10864
  Options:
9977
- -p, --ws-port <port> WebSocket server port (default: 8080)
9978
- -t, --target <host:port> Target TCP service (default: localhost:8081)
9979
- -d, --debug Enable debug logging
9980
- -h, --help Show this help
10865
+ -p, --ws-port <port> WebSocket server port (default: 8080)
10866
+ -t, --target <host:port> Target TCP service (default: localhost:8081)
10867
+ -d, --debug Enable debug logging
10868
+ -w, --ws-compression Enable WebSocket side compression
10869
+ -c, --tcp-compression Enable TCP side compression
10870
+ --compression-threshold <n> Minimum size to compress (default: 1024)
10871
+ -h, --help Show this help
9981
10872
 
9982
- Example:
10873
+ Examples:
9983
10874
  npx @naeemo/capnp proxy -p 9000 -t 192.168.1.100:7000
10875
+
10876
+ # Enable compression on WebSocket side only (for browser clients)
10877
+ npx @naeemo/capnp proxy -p 9000 -t localhost:7000 -w
10878
+
10879
+ # Enable compression on both sides
10880
+ npx @naeemo/capnp proxy -p 9000 -t localhost:7000 -w -c
10881
+
10882
+ # Enable compression with lower threshold
10883
+ npx @naeemo/capnp proxy -p 9000 -t localhost:7000 -w --compression-threshold 512
9984
10884
  `);
9985
10885
  process.exit(0);
9986
10886
  }
@@ -9988,11 +10888,26 @@ Example:
9988
10888
  wsPort,
9989
10889
  targetHost,
9990
10890
  targetPort,
9991
- debug
10891
+ debug,
10892
+ compression: {
10893
+ ws: {
10894
+ enabled: wsCompression,
10895
+ threshold: compressionThreshold
10896
+ },
10897
+ tcp: {
10898
+ enabled: tcpCompression,
10899
+ threshold: compressionThreshold
10900
+ }
10901
+ }
9992
10902
  });
9993
10903
  console.log("WebSocket-to-TCP Proxy started");
9994
10904
  console.log(` WebSocket: ws://localhost:${wsPort}`);
9995
10905
  console.log(` Target: ${targetHost}:${targetPort}`);
10906
+ if (wsCompression || tcpCompression) {
10907
+ console.log(" Compression:");
10908
+ console.log(` WebSocket: ${wsCompression ? "enabled" : "disabled"} (threshold: ${compressionThreshold})`);
10909
+ console.log(` TCP: ${tcpCompression ? "enabled" : "disabled"} (threshold: ${compressionThreshold})`);
10910
+ }
9996
10911
  proxy.on("connection", () => {
9997
10912
  console.log(`Active connections: ${proxy.getConnectionCount()}`);
9998
10913
  });
@@ -10014,6 +10929,7 @@ exports.BulkTransferManager = BulkTransferManager;
10014
10929
  exports.CapnpWebSocketProxy = CapnpWebSocketProxy;
10015
10930
  exports.ConnectionManager = ConnectionManager;
10016
10931
  exports.DEFAULT_BULK_CONFIG = DEFAULT_BULK_CONFIG;
10932
+ exports.DEFAULT_COMPRESSION_CONFIG = DEFAULT_COMPRESSION_CONFIG;
10017
10933
  exports.DEFAULT_ESCROW_CONFIG = DEFAULT_ESCROW_CONFIG;
10018
10934
  exports.DEFAULT_FLOW_CONTROL = DEFAULT_FLOW_CONTROL;
10019
10935
  exports.DEFAULT_JOIN_OPTIONS = DEFAULT_JOIN_OPTIONS;
@@ -10024,6 +10940,7 @@ exports.DropPolicy = DropPolicy;
10024
10940
  exports.ElementSize = ElementSize;
10025
10941
  exports.ExportTable = require_rpc_connection.ExportTable;
10026
10942
  exports.EzRpcTransport = EzRpcTransport;
10943
+ exports.FrameFlags = FrameFlags;
10027
10944
  exports.ImportTable = require_rpc_connection.ImportTable;
10028
10945
  exports.Level3Handlers = Level3Handlers;
10029
10946
  exports.Level4Handlers = Level4Handlers;
@@ -10062,8 +10979,11 @@ exports.UnionBuilder = UnionBuilder;
10062
10979
  exports.UnionReader = UnionReader;
10063
10980
  exports.WORD_SIZE = WORD_SIZE;
10064
10981
  exports.WebSocketTransport = WebSocketTransport;
10982
+ exports.compress = compress;
10065
10983
  exports.configureGlobalMemoryPool = configureGlobalMemoryPool;
10066
10984
  exports.createBulkTransferManager = createBulkTransferManager;
10985
+ exports.createCompressionConfig = createCompressionConfig;
10986
+ exports.createCompressionStats = createCompressionStats;
10067
10987
  exports.createDynamicReader = createDynamicReader;
10068
10988
  exports.createDynamicReaderByTypeId = createDynamicReaderByTypeId;
10069
10989
  exports.createDynamicReaderFromStruct = createDynamicReaderFromStruct;
@@ -10091,17 +11011,20 @@ exports.deserializeSturdyRef = deserializeSturdyRef;
10091
11011
  exports.disableDebug = require_rpc_connection.disableDebug;
10092
11012
  exports.dumpDynamicReader = dumpDynamicReader;
10093
11013
  exports.enableDebug = require_rpc_connection.enableDebug;
11014
+ exports.encodeFarPointer = encodeFarPointer;
10094
11015
  exports.encodeListPointer = encodeListPointer;
10095
11016
  exports.encodeStructPointer = encodeStructPointer;
10096
11017
  exports.fastCopy = fastCopy;
10097
11018
  exports.generateProvisionId = generateProvisionId;
10098
11019
  exports.generateVatId = generateVatId;
10099
11020
  exports.getGlobalMemoryPool = getGlobalMemoryPool;
11021
+ exports.isCompressionFrame = isCompressionFrame;
10100
11022
  exports.isDebugEnabled = require_rpc_connection.isDebugEnabled;
10101
11023
  exports.isPipelineClient = require_rpc_connection.isPipelineClient;
10102
11024
  exports.isSameBuffer = isSameBuffer;
10103
11025
  exports.isStream = isStream;
10104
11026
  exports.isSturdyRefValid = isSturdyRefValid;
11027
+ exports.parseFrameHeader = parseFrameHeader;
10105
11028
  exports.parseSchemaNodes = require_rpc_connection.parseSchemaNodes;
10106
11029
  exports.serializeDynamic = serializeDynamic;
10107
11030
  exports.serializeDynamicByTypeId = serializeDynamicByTypeId;
@@ -10113,4 +11036,6 @@ exports.serializeSchemaRequest = require_rpc_connection.serializeSchemaRequest;
10113
11036
  exports.serializeSchemaResponse = require_rpc_connection.serializeSchemaResponse;
10114
11037
  exports.serializeSturdyRef = serializeSturdyRef;
10115
11038
  exports.supportsStreaming = supportsStreaming;
11039
+ exports.tryDecompress = tryDecompress;
11040
+ exports.uncompress = uncompress;
10116
11041
  //# sourceMappingURL=index.cjs.map