connectbase-client 3.6.0 → 3.7.1

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/cli.js CHANGED
@@ -1431,10 +1431,36 @@ function createWsPongFrame() {
1431
1431
  header[1] = 128 | 0;
1432
1432
  return Buffer.concat([header, maskKey]);
1433
1433
  }
1434
+ function createWsBinaryFrame(payload) {
1435
+ const len = payload.length;
1436
+ const maskKey = crypto.randomBytes(4);
1437
+ let header;
1438
+ if (len < 126) {
1439
+ header = Buffer.alloc(2);
1440
+ header[0] = 130;
1441
+ header[1] = 128 | len;
1442
+ } else if (len < 65536) {
1443
+ header = Buffer.alloc(4);
1444
+ header[0] = 130;
1445
+ header[1] = 128 | 126;
1446
+ header.writeUInt16BE(len, 2);
1447
+ } else {
1448
+ header = Buffer.alloc(10);
1449
+ header[0] = 130;
1450
+ header[1] = 128 | 127;
1451
+ header.writeBigUInt64BE(BigInt(len), 2);
1452
+ }
1453
+ const masked = Buffer.alloc(len);
1454
+ for (let i = 0; i < len; i++) {
1455
+ masked[i] = payload[i] ^ maskKey[i % 4];
1456
+ }
1457
+ return Buffer.concat([header, maskKey, masked]);
1458
+ }
1434
1459
  var WsFrameParser = class {
1435
1460
  constructor(handlers) {
1436
1461
  this.buffer = Buffer.alloc(0);
1437
1462
  this.onMessage = handlers.onMessage;
1463
+ this.onBinary = handlers.onBinary;
1438
1464
  this.onClose = handlers.onClose;
1439
1465
  this.onPing = handlers.onPing;
1440
1466
  }
@@ -1479,6 +1505,13 @@ var WsFrameParser = class {
1479
1505
  case 1:
1480
1506
  this.onMessage(payload.toString("utf-8"));
1481
1507
  break;
1508
+ case 2:
1509
+ if (this.onBinary) {
1510
+ const copy = Buffer.alloc(payload.length);
1511
+ payload.copy(copy);
1512
+ this.onBinary(copy);
1513
+ }
1514
+ break;
1482
1515
  case 8:
1483
1516
  this.onClose();
1484
1517
  break;
@@ -1664,7 +1697,7 @@ async function startTunnel(port, config, tunnelOpts) {
1664
1697
  const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
1665
1698
  const parsedUrl = new URL(tunnelServerUrl);
1666
1699
  const isHttps = parsedUrl.protocol === "https:";
1667
- let wsPath = `/v1/tunnel/connect?app_id=${encodeURIComponent(appId)}&local_port=${port}`;
1700
+ let wsPath = `/v2/tunnel/connect?app_id=${encodeURIComponent(appId)}&local_port=${port}`;
1668
1701
  if (tunnelOpts?.timeout) {
1669
1702
  wsPath += `&timeout=${tunnelOpts.timeout}`;
1670
1703
  }
@@ -1741,6 +1774,15 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
1741
1774
  warn(`\uBA54\uC2DC\uC9C0 \uD30C\uC2F1 \uC2E4\uD328: ${e}`);
1742
1775
  }
1743
1776
  },
1777
+ onBinary: (data) => {
1778
+ if (data.length < 8) {
1779
+ warn(`v2 binary frame too short (${data.length} bytes)`);
1780
+ return;
1781
+ }
1782
+ const streamId = data.readBigUInt64BE(0).toString();
1783
+ const payload = data.subarray(8);
1784
+ routeStreamData(streamId, payload);
1785
+ },
1744
1786
  onClose: () => {
1745
1787
  info("\uC11C\uBC84\uAC00 \uC5F0\uACB0\uC744 \uC885\uB8CC\uD588\uC2B5\uB2C8\uB2E4");
1746
1788
  sock.destroy();
@@ -1804,6 +1846,192 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
1804
1846
  info(`${(delay / 1e3).toFixed(0)}\uCD08 \uD6C4 \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4... (${reconnectAttempts}/${maxReconnectAttempts})`);
1805
1847
  setTimeout(connect, delay);
1806
1848
  }
1849
+ const streams = /* @__PURE__ */ new Map();
1850
+ function routeStreamData(streamId, payload) {
1851
+ const f = streams.get(streamId);
1852
+ if (!f) return;
1853
+ f.feedBody(payload);
1854
+ }
1855
+ function sendControl(sock, msg) {
1856
+ try {
1857
+ sock.write(createWsTextFrame(JSON.stringify(msg)));
1858
+ } catch {
1859
+ }
1860
+ }
1861
+ function sendBinary(sock, streamIdStr, payload) {
1862
+ try {
1863
+ const header = Buffer.alloc(8);
1864
+ header.writeBigUInt64BE(BigInt(streamIdStr), 0);
1865
+ sock.write(createWsBinaryFrame(Buffer.concat([header, payload])));
1866
+ } catch {
1867
+ }
1868
+ }
1869
+ function startHTTPStream(sock, streamId, open, localPort) {
1870
+ const method = open.method || "GET";
1871
+ const reqPath = open.path || "/";
1872
+ const query = open.query || "";
1873
+ const headers = open.headers || {};
1874
+ const fullPath = query ? `${reqPath}?${query}` : reqPath;
1875
+ const localHeaders = {};
1876
+ for (const [k, v] of Object.entries(headers)) {
1877
+ if (k.toLowerCase() !== "host") localHeaders[k] = v;
1878
+ }
1879
+ localHeaders["host"] = `localhost:${localPort}`;
1880
+ let started = false;
1881
+ let cancelled = false;
1882
+ const localReq = http.request(
1883
+ { hostname: "127.0.0.1", port: localPort, path: fullPath, method, headers: localHeaders },
1884
+ (res) => {
1885
+ const respHeaders = {};
1886
+ for (const [k, v] of Object.entries(res.headers)) {
1887
+ if (v == null) continue;
1888
+ respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
1889
+ }
1890
+ started = true;
1891
+ sendControl(sock, {
1892
+ type: "stream_response",
1893
+ stream_id: streamId,
1894
+ status: res.statusCode || 502,
1895
+ headers: respHeaders
1896
+ });
1897
+ const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
1898
+ log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`);
1899
+ res.on("data", (chunk) => {
1900
+ if (cancelled) return;
1901
+ sendBinary(sock, streamId, chunk);
1902
+ });
1903
+ res.on("end", () => {
1904
+ if (cancelled) return;
1905
+ sendControl(sock, { type: "stream_eof", stream_id: streamId, side: "upstream" });
1906
+ sendControl(sock, { type: "stream_close", stream_id: streamId });
1907
+ streams.delete(streamId);
1908
+ });
1909
+ res.on("error", (err) => {
1910
+ sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
1911
+ streams.delete(streamId);
1912
+ });
1913
+ }
1914
+ );
1915
+ localReq.on("error", (err) => {
1916
+ warn(`\uB85C\uCEEC \uC11C\uBC84 \uC5F0\uACB0 \uC2E4\uD328 (${method} ${reqPath}): ${err.message}`);
1917
+ if (!started) {
1918
+ sendControl(sock, {
1919
+ type: "stream_response",
1920
+ stream_id: streamId,
1921
+ status: 502,
1922
+ headers: { "content-type": "application/json" }
1923
+ });
1924
+ sendBinary(sock, streamId, Buffer.from(JSON.stringify({ error: `Local server error: ${err.message}` })));
1925
+ }
1926
+ sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
1927
+ streams.delete(streamId);
1928
+ });
1929
+ const forwarder = {
1930
+ kind: "http",
1931
+ feedBody: (chunk) => {
1932
+ if (!cancelled) localReq.write(chunk);
1933
+ },
1934
+ endBody: () => {
1935
+ if (!cancelled) localReq.end();
1936
+ },
1937
+ cancel: () => {
1938
+ cancelled = true;
1939
+ try {
1940
+ localReq.destroy();
1941
+ } catch {
1942
+ }
1943
+ }
1944
+ };
1945
+ streams.set(streamId, forwarder);
1946
+ }
1947
+ function startWSStream(sock, streamId, open, localPort) {
1948
+ const reqPath = open.path || "/";
1949
+ const query = open.query || "";
1950
+ const headers = open.headers || {};
1951
+ const fullPath = query ? `${reqPath}?${query}` : reqPath;
1952
+ const localHeaders = {};
1953
+ for (const [k, v] of Object.entries(headers)) {
1954
+ if (k.toLowerCase() !== "host") localHeaders[k] = v;
1955
+ }
1956
+ localHeaders["host"] = `localhost:${localPort}`;
1957
+ localHeaders["connection"] = "Upgrade";
1958
+ localHeaders["upgrade"] = "websocket";
1959
+ let cancelled = false;
1960
+ let upstream = null;
1961
+ const req = http.request({
1962
+ hostname: "127.0.0.1",
1963
+ port: localPort,
1964
+ path: fullPath,
1965
+ method: "GET",
1966
+ headers: localHeaders
1967
+ });
1968
+ req.on("upgrade", (res, sk) => {
1969
+ upstream = sk;
1970
+ const respHeaders = {};
1971
+ for (const [k, v] of Object.entries(res.headers)) {
1972
+ if (v == null) continue;
1973
+ respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
1974
+ }
1975
+ sendControl(sock, {
1976
+ type: "stream_response",
1977
+ stream_id: streamId,
1978
+ status: res.statusCode || 101,
1979
+ headers: respHeaders,
1980
+ websocket: true
1981
+ });
1982
+ log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${colors.cyan}WS${colors.reset} ${reqPath} \u2192 101`);
1983
+ sk.on("data", (chunk) => {
1984
+ if (cancelled) return;
1985
+ sendBinary(sock, streamId, chunk);
1986
+ });
1987
+ sk.on("close", () => {
1988
+ if (cancelled) return;
1989
+ sendControl(sock, { type: "stream_close", stream_id: streamId });
1990
+ streams.delete(streamId);
1991
+ });
1992
+ sk.on("error", (err) => {
1993
+ sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
1994
+ streams.delete(streamId);
1995
+ });
1996
+ });
1997
+ req.on("response", (res) => {
1998
+ const respHeaders = {};
1999
+ for (const [k, v] of Object.entries(res.headers)) {
2000
+ if (v == null) continue;
2001
+ respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
2002
+ }
2003
+ sendControl(sock, {
2004
+ type: "stream_response",
2005
+ stream_id: streamId,
2006
+ status: res.statusCode || 502,
2007
+ headers: respHeaders,
2008
+ websocket: false
2009
+ });
2010
+ sendControl(sock, { type: "stream_close", stream_id: streamId });
2011
+ streams.delete(streamId);
2012
+ });
2013
+ req.on("error", (err) => {
2014
+ sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
2015
+ streams.delete(streamId);
2016
+ });
2017
+ req.end();
2018
+ const forwarder = {
2019
+ kind: "ws",
2020
+ feedBody: (chunk) => {
2021
+ if (!cancelled && upstream) upstream.write(chunk);
2022
+ },
2023
+ endBody: () => {
2024
+ },
2025
+ cancel: () => {
2026
+ cancelled = true;
2027
+ try {
2028
+ upstream?.destroy();
2029
+ } catch {
2030
+ }
2031
+ }
2032
+ };
2033
+ streams.set(streamId, forwarder);
2034
+ }
1807
2035
  async function handleMessage(msg, sock, localPort) {
1808
2036
  switch (msg.type) {
1809
2037
  case "tunnel_ready": {
@@ -1851,9 +2079,38 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
1851
2079
  `);
1852
2080
  break;
1853
2081
  }
1854
- case "http_request":
1855
- forwardRequest(msg, sock, localPort);
2082
+ case "stream_open": {
2083
+ const sid = String(msg.stream_id ?? "");
2084
+ if (!sid) {
2085
+ warn("stream_open with empty stream_id");
2086
+ break;
2087
+ }
2088
+ const kind = msg.kind || "http";
2089
+ if (kind === "ws") {
2090
+ startWSStream(sock, sid, msg, localPort);
2091
+ } else {
2092
+ startHTTPStream(sock, sid, msg, localPort);
2093
+ }
1856
2094
  break;
2095
+ }
2096
+ case "stream_eof": {
2097
+ const sid = String(msg.stream_id ?? "");
2098
+ const side = msg.side;
2099
+ if (side === "client") {
2100
+ const f = streams.get(sid);
2101
+ f?.endBody();
2102
+ }
2103
+ break;
2104
+ }
2105
+ case "stream_close": {
2106
+ const sid = String(msg.stream_id ?? "");
2107
+ const f = streams.get(sid);
2108
+ if (f) {
2109
+ f.cancel();
2110
+ streams.delete(sid);
2111
+ }
2112
+ break;
2113
+ }
1857
2114
  case "tunnel_error": {
1858
2115
  const result = handleTunnelError(msg, appId, localPort);
1859
2116
  error(result.message);
@@ -1868,121 +2125,6 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
1868
2125
  break;
1869
2126
  }
1870
2127
  }
1871
- function forwardRequest(msg, sock, localPort) {
1872
- const requestId = msg.request_id;
1873
- const method = msg.method;
1874
- const reqPath = msg.path;
1875
- const query = msg.query || "";
1876
- const headers = msg.headers || {};
1877
- const bodyBase64 = msg.body;
1878
- const fullPath = query ? `${reqPath}?${query}` : reqPath;
1879
- const localHeaders = {};
1880
- for (const [key, value] of Object.entries(headers)) {
1881
- if (key.toLowerCase() !== "host") {
1882
- localHeaders[key] = value;
1883
- }
1884
- }
1885
- localHeaders["host"] = `localhost:${localPort}`;
1886
- const reqOptions = {
1887
- hostname: "127.0.0.1",
1888
- port: localPort,
1889
- path: fullPath,
1890
- method,
1891
- headers: localHeaders
1892
- };
1893
- const localReq = http.request(reqOptions, (res) => {
1894
- const responseHeaders = {};
1895
- for (const [key, value] of Object.entries(res.headers)) {
1896
- if (value) responseHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
1897
- }
1898
- const contentType = (responseHeaders["content-type"] || "").toLowerCase();
1899
- const transferEncoding = (responseHeaders["transfer-encoding"] || "").toLowerCase();
1900
- const isStreaming = contentType.includes("text/event-stream") || transferEncoding.includes("chunked");
1901
- if (isStreaming) {
1902
- try {
1903
- sock.write(createWsTextFrame(JSON.stringify({
1904
- type: "http_response_start",
1905
- request_id: requestId,
1906
- status: res.statusCode || 200,
1907
- headers: responseHeaders
1908
- })));
1909
- } catch {
1910
- warn(`\uC2A4\uD2B8\uB9AC\uBC0D \uC2DC\uC791 \uC804\uC1A1 \uC2E4\uD328: ${requestId}`);
1911
- return;
1912
- }
1913
- const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
1914
- log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode} ${colors.cyan}[stream]${colors.reset}`);
1915
- res.on("data", (chunk) => {
1916
- try {
1917
- sock.write(createWsTextFrame(JSON.stringify({
1918
- type: "http_response_chunk",
1919
- request_id: requestId,
1920
- data: chunk.toString("base64")
1921
- })));
1922
- } catch {
1923
- warn(`\uC2A4\uD2B8\uB9AC\uBC0D \uCCAD\uD06C \uC804\uC1A1 \uC2E4\uD328: ${requestId}`);
1924
- }
1925
- });
1926
- res.on("end", () => {
1927
- try {
1928
- sock.write(createWsTextFrame(JSON.stringify({
1929
- type: "http_response_end",
1930
- request_id: requestId
1931
- })));
1932
- } catch {
1933
- }
1934
- });
1935
- res.on("error", (err) => {
1936
- try {
1937
- sock.write(createWsTextFrame(JSON.stringify({
1938
- type: "http_response_error",
1939
- request_id: requestId,
1940
- error: err.message
1941
- })));
1942
- } catch {
1943
- }
1944
- });
1945
- } else {
1946
- const chunks = [];
1947
- res.on("data", (chunk) => chunks.push(chunk));
1948
- res.on("end", () => {
1949
- const body = Buffer.concat(chunks);
1950
- const response = {
1951
- type: "http_response",
1952
- request_id: requestId,
1953
- status: res.statusCode || 200,
1954
- headers: responseHeaders,
1955
- body: body.length > 0 ? body.toString("base64") : ""
1956
- };
1957
- try {
1958
- sock.write(createWsTextFrame(JSON.stringify(response)));
1959
- const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
1960
- log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`);
1961
- } catch {
1962
- warn(`\uC751\uB2F5 \uC804\uC1A1 \uC2E4\uD328: ${requestId}`);
1963
- }
1964
- });
1965
- }
1966
- });
1967
- localReq.on("error", (err) => {
1968
- const response = {
1969
- type: "http_response",
1970
- request_id: requestId,
1971
- status: 502,
1972
- headers: { "content-type": "application/json" },
1973
- body: Buffer.from(JSON.stringify({ error: `Local server error: ${err.message}` })).toString("base64")
1974
- };
1975
- try {
1976
- sock.write(createWsTextFrame(JSON.stringify(response)));
1977
- } catch {
1978
- }
1979
- warn(`\uB85C\uCEEC \uC11C\uBC84 \uC5F0\uACB0 \uC2E4\uD328 (${method} ${reqPath}): ${err.message}`);
1980
- });
1981
- if (bodyBase64) {
1982
- localReq.write(Buffer.from(bodyBase64, "base64"));
1983
- }
1984
- localReq.end();
1985
- }
1986
2128
  connect();
1987
2129
  await new Promise(() => {
1988
2130
  });