From 696af83fc212fd25cd2e5d29235914decee7d977 Mon Sep 17 00:00:00 2001 From: banknight Date: Sun, 31 Dec 2023 22:29:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0docker=20=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9docker=20compose=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=BC=96=E8=AF=91docker=E9=95=9C=E5=83=8F=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + .github/workflows/docker_build.yml | 53 ++ Dockerfile | 33 + README.md | 65 +- docker-compose.yml | 11 + package-lock.json | 1054 ++++++++++++++-------------- package.json | 69 +- src/index.ts | 681 +++++++++++------- src/types.ts | 45 +- src/utils.ts | 130 ++-- 10 files changed, 1258 insertions(+), 884 deletions(-) create mode 100644 .github/workflows/docker_build.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example index b237e9d..a74beca 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ PORT=1080 +WSPATH=/vless UUID=13170fcc-1966-507d-bce9-532cc588fcf3 \ No newline at end of file diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml new file mode 100644 index 0000000..5b234b0 --- /dev/null +++ b/.github/workflows/docker_build.yml @@ -0,0 +1,53 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow +name: Build Docker Image + +# 当 push 到 master 分支,或者创建以 v 开头的 tag 时触发,可根据需求修改 +on: + push: + branches: + - main + tags: + - v* + +env: + REGISTRY: ghcr.io + IMAGE: BanKnight/nvless + +jobs: + build-and-push: + runs-on: ubuntu-latest + + # 这里用于定义 GITHUB_TOKEN 的权限 + permissions: + packages: write + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # 登录到 GitHub Packages 容器仓库 + # 注意 secrets.GITHUB_TOKEN 不需要手动添加,直接就可以用 + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # 根据输入自动生成 tag 和 label 等数据,说明见下 + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE }} + + # 构建并上传 + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f4053cb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# 基于Node.js 20版本的Slim镜像构建 +FROM node:20-slim AS build-env + +# 安装 jemalloc +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + libjemalloc2 && \ + rm -rf /var/lib/apt/lists/* + + +COPY . /app +# 设置工作目录 +WORKDIR /app + +# 安装项目依赖 +RUN npm ci --omit=dev + +FROM gcr.io/distroless/nodejs20-debian12 + +COPY --from=build-env /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/x86_64-linux-gnu/ + +# 设置环境变量使 jemalloc 全局起效 +ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 + +COPY --from=build-env /app /app + +WORKDIR /app + +# 暴露应用程序使用的端口(根据你的应用程序进行修改) +EXPOSE 3000 + +# 运行应用程序,这里不加载env文件了,交给用户自己搞定 +CMD ["--import", "tsx", "src/index.ts"] diff --git a/README.md b/README.md index 08d9aae..22ce38b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,63 @@ -# nvless -nodejs vless +## nvless + +一个用typescript实现的只使用ws链接的vless协议服务端。 + +### 为什么重复造轮子 + ++ 研究nodejs在这方面的极限 ++ 研究nodejs中的内存和cpu的控制 ++ 在已有vps的基础上,如何简化配置 + +### 特性 + ++ 支持 tcp 协议 ++ 支持 udp 协议(需要完整测试) ++ 支持 mux tcp 协议 ++ 支持 mux udp 协议(需要完整测试) ++ 内存:占用小 ++ cpu:占用低 ++ 支持 docker ++ 支持:配置文件/环境变量 + +## 如何开始 + +## docker compose 部署 + ++ 下载文件 [docker-compose.yml](./docker-compose.yml) ++ 修改配置文件: + + 端口:默认3000 + + uuid:默认 13170fcc-1966-507d-bce9-532cc588fcf3 + + 路径:默认 /nvless ++ 启动:`docker-compose up -d` ++ 注意:开启防火墙端口 ++ 注意:要支持https,请部署在nginx之后,推荐使用 [1Panel](https://github.com/1Panel-dev/1Panel) 部署 ++ nginx 配置参考: + +```nginx +location = /nvless { + proxy_pass http://127.0.0.1:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + add_header Cache-Control no-cache; +} +``` + +### 本地调试 + ++ 拷贝 [.env.example](./.env.example) 一份为本地配置 `.env.local` ++ 修改配置文件 ++ 启动 + +```bash +# 安装依赖 +npm install + +# 启动服务 +npm run test +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..88139c4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3' +services: + nvless: + image: ghcr.io/banknight/nvless:main + container_name: nvless + restart: always + ports: + - 3000:3000 + environment: + - UUID=13170fcc-1966-507d-bce9-532cc588fcf3 + - WSPATH=/nvless diff --git a/package-lock.json b/package-lock.json index 637f3b7..5222230 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,531 +1,531 @@ { - "name": "nvless", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "nvless", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "tsx": "^4.7.0", - "uuid": "^9.0.1", - "ws": "^8.16.0" - }, - "devDependencies": { - "@types/node": "^20.10.6", - "@types/uuid": "^9.0.7", - "@types/ws": "^8.5.10", - "typescript": "^5.3.3" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", - "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", - "cpu": [ - "loong64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", - "cpu": [ - "mips64el" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@types/node": { - "version": "20.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", - "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/uuid": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", - "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", - "dev": true - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tsx": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.0.tgz", - "integrity": "sha512-I+t79RYPlEYlHn9a+KzwrvEwhJg35h/1zHsLC2JXvhC2mdynMv6Zxzvhv5EMV6VF5qJlLlkSnMVvdZV3PSIGcg==", - "dependencies": { - "esbuild": "~0.19.10", - "get-tsconfig": "^4.7.2" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true + "name": "nvless", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nvless", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "tsx": "^4.7.0", + "uuid": "^9.0.1", + "ws": "^8.16.0" + }, + "devDependencies": { + "@types/node": "^20.10.6", + "@types/uuid": "^9.0.7", + "@types/ws": "^8.5.10", + "typescript": "^5.3.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", + "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", + "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", + "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", + "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", + "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", + "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", + "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", + "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", + "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", + "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", + "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", + "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", + "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", + "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", + "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", + "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", + "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", + "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", + "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", + "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", + "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", + "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", + "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/node": { + "version": "20.10.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", + "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/esbuild": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", + "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.11", + "@esbuild/android-arm": "0.19.11", + "@esbuild/android-arm64": "0.19.11", + "@esbuild/android-x64": "0.19.11", + "@esbuild/darwin-arm64": "0.19.11", + "@esbuild/darwin-x64": "0.19.11", + "@esbuild/freebsd-arm64": "0.19.11", + "@esbuild/freebsd-x64": "0.19.11", + "@esbuild/linux-arm": "0.19.11", + "@esbuild/linux-arm64": "0.19.11", + "@esbuild/linux-ia32": "0.19.11", + "@esbuild/linux-loong64": "0.19.11", + "@esbuild/linux-mips64el": "0.19.11", + "@esbuild/linux-ppc64": "0.19.11", + "@esbuild/linux-riscv64": "0.19.11", + "@esbuild/linux-s390x": "0.19.11", + "@esbuild/linux-x64": "0.19.11", + "@esbuild/netbsd-x64": "0.19.11", + "@esbuild/openbsd-x64": "0.19.11", + "@esbuild/sunos-x64": "0.19.11", + "@esbuild/win32-arm64": "0.19.11", + "@esbuild/win32-ia32": "0.19.11", + "@esbuild/win32-x64": "0.19.11" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.0.tgz", + "integrity": "sha512-I+t79RYPlEYlHn9a+KzwrvEwhJg35h/1zHsLC2JXvhC2mdynMv6Zxzvhv5EMV6VF5qJlLlkSnMVvdZV3PSIGcg==", + "dependencies": { + "esbuild": "~0.19.10", + "get-tsconfig": "^4.7.2" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } - } } - } } diff --git a/package.json b/package.json index 2428c4b..8ed5c29 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,36 @@ { - "name": "nvless", - "version": "0.0.1", - "description": "nodejs vless", - "main": "index.js", - "scripts": { - "test": "node --env-file=.env.local --import tsx src/index.ts" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/BanKnight/nvless.git" - }, - "keywords": [ - "node", - "vless", - "proxy" - ], - "author": "banknight", - "license": "MIT", - "bugs": { - "url": "https://github.com/BanKnight/nvless/issues" - }, - "homepage": "https://github.com/BanKnight/nvless#readme", - "dependencies": { - "tsx": "^4.7.0", - "uuid": "^9.0.1", - "ws": "^8.16.0" - }, - "devDependencies": { - "@types/node": "^20.10.6", - "@types/uuid": "^9.0.7", - "@types/ws": "^8.5.10", - "typescript": "^5.3.3" - } -} + "name": "nvless", + "version": "0.0.1", + "description": "nodejs vless", + "main": "index.js", + "scripts": { + "test": "node --env-file=.env.local --import tsx src/index.ts", + "start": "node --env-file=.env --import tsx src/index.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/BanKnight/nvless.git" + }, + "keywords": [ + "node", + "vless", + "proxy" + ], + "author": "banknight", + "license": "MIT", + "bugs": { + "url": "https://github.com/BanKnight/nvless/issues" + }, + "homepage": "https://github.com/BanKnight/nvless#readme", + "dependencies": { + "tsx": "^4.7.0", + "uuid": "^9.0.1", + "ws": "^8.16.0" + }, + "devDependencies": { + "@types/node": "^20.10.6", + "@types/uuid": "^9.0.7", + "@types/ws": "^8.5.10", + "typescript": "^5.3.3" + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 13baef7..64918d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,41 +1,99 @@ import { stringify, v5, validate } from "uuid" -import { WebSocketServer, WebSocket, createWebSocketStream } from "ws"; +import { WebSocket, createWebSocketStream, WebSocketServer } from "ws"; import { createConnection } from "net"; -import { createSocket } from "dgram"; +import { RemoteInfo, createSocket } from "dgram"; -import { read_address } from "./utils"; -import { Dest, MuxSession } from "./types"; +import { readAddress, readMetaAddress, writeMetaAddress } from "./utils"; +import { Dest, MuxSession, NameProtocols } from "./types"; import { finished } from "stream"; -const PORT = parseInt(process.env.PORT ?? "1080") +const PORT = parseInt(process.env.PORT ?? "3000") const UUID = validate(process.env.UUID ?? "") ? - process.env.UUID : v5(process.env.UUID!, Buffer.alloc(16)) + process.env.UUID : v5(process.env.UUID ?? "inputyouruuid", Buffer.alloc(16)) -let start_index = Date.now() +const WSPATH = process.env.WSPATH ?? "" + +let idHelper = 1 const sessions = {} as Record -const wss = new WebSocketServer({ port: PORT,host:"0.0.0.0" }) +const wss = new WebSocketServer({ port: PORT, host: "0.0.0.0", path: WSPATH }) + +let connecting = 0 +let lastConnecting = 0 + +wss.on("connection", (socket: WebSocket) => { + + connecting++ + + socket.id = idHelper++ + socket.pendings = [] + socket.next = head.bind(null, socket) + socket.setMaxListeners(100) + + //@ts-ignore + // console.log("New connection from", socket.id, socket._socket.remoteAddress, socket._socket.remotePort) + + socket.on("message", (data: Buffer, isBinary) => { + socket.pendings.push(data) + socket.next() + }) + + socket.on("error", () => { + socket.close() + }) -wss.on("connection", (ws: WebSocket) => { + socket.once("close", () => { + connecting-- + // console.log("close", socket.id) + }) - ws.id = start_index++ - ws.pendings = [] - ws.next = head.bind(null, ws) + socket.once("close", () => { + const prefix = socket.id.toString() - ws.on("message", (data: Buffer, isBinary) => { - ws.pendings.push(data) - ws.next() + for (const id in sessions) { + let session = sessions[id] + if (!id.startsWith(prefix)) { + continue + } + delete sessions[id] + session?.close?.() + } }) }) -const protocol_type_to_name = { - 1: "tcp", - 2: "udp", - 3: "mux" -} +setInterval(() => { + if (connecting != lastConnecting) { + console.log("Connections alive:", connecting) + } + lastConnecting = connecting +}, 5000) + +wss.on('error', (e: any) => { + + if (e.code == 'EADDRINUSE') { + console.error(e) + //Retry + return + } + + // if (proxy.server.force) { + // return + // } +}); + +wss.on("listening", () => { + console.log(`listening on ${PORT},uuid:${UUID},path:${WSPATH}`) + console.log(`vless://${UUID}@127.0.0.1:${PORT}?host=localhost&path=${WSPATH}&type=ws&encryption=none&fp=random&sni=localhost#nvless`) + console.log("注意修改ip 域名 ") +}) + + +// 仅在发送的时候才用到的buffer +const BUFFER_META_RESERVE = Buffer.allocUnsafe(64) +const BUFFER_LEN_RESERVE = Buffer.allocUnsafe(2) -const vless_success_resp = Buffer.from([0, 0]) +const BUFFER_SUCCESS_RESP = Buffer.from([0, 0]) function head(socket: WebSocket) { @@ -54,9 +112,9 @@ function head(socket: WebSocket) { const cmd = buffer[offset++] //18+optLength //@ts-ignore - let protocol = protocol_type_to_name[cmd!] + let protocol = NameProtocols[cmd!] if (protocol == null) { - console.error(new Error(`unsupported type:${cmd}`)) + console.error("unsupported type:", cmd) socket.close() return } @@ -66,59 +124,53 @@ function head(socket: WebSocket) { const ip = socket._socket.remoteAddress; //@ts-ignore const port = socket._socket.remotePort - console.error(new Error(`auth failed type:${cmd} ${ip}:${port}`)) + console.error("auth failed type:", cmd, ip, port) socket.close() return } - if (protocol == "mux") //mux - { - socket.pendings.push(buffer.subarray(offset)) + if (protocol == "mux") { //mux + const head = buffer.subarray(offset) + socket.pendings.push(head) socket.next = mux.bind(null, socket) + socket.send(BUFFER_SUCCESS_RESP) mux(socket) return } - const dest = { - host: "", + //@ts-ignore + const dest: Dest = { protocol, - port: buffer.readUInt16BE(offset) as unknown as number, - user: userid, - version: version!, + port: buffer.readUint16BE(offset), } offset += 2 + offset = readAddress(buffer, dest, offset) - offset = read_address(buffer, dest, offset) - if (!dest.host) { - console.error(new Error(`invalid addressType`)) + if (!dest.host || dest.host.length == 0 || !dest.port) { + console.error("invalid addressType:", dest.host, dest.port) socket.close() return } - // socket.pendings = null socket.removeAllListeners("message") - const head = buffer.subarray(offset) - if (head.length > 0) { - socket.pendings.unshift(head) - } + socket.send(BUFFER_SUCCESS_RESP) - socket.send(vless_success_resp) + const head = buffer.subarray(offset) - switch (cmd) { - case 0x01: //tcp - tcp(socket, dest) + switch (dest.protocol) { + case "tcp": //tcp + tcp(socket, dest, head) break - case 0x02: //udp - udp(socket, dest) + case "udp": //udp + udp(socket, dest, head) break - default: //mux - console.error(new Error(`unsupported type:${cmd}`)) + default: //unknown + console.error("unsupported dest.protocol type", cmd) socket.close() break } - } /** @@ -128,7 +180,7 @@ function head(socket: WebSocket) { * @param at_least 要满足的最小数量 * @returns */ -function fetch(socket: WebSocket, at_least: number) { +function fetch(socket: { pendings: Array }, at_least: number) { let total = 0 for (let one of socket.pendings) { total += one.length @@ -145,9 +197,8 @@ function fetch(socket: WebSocket, at_least: number) { let offset = 0 while (socket.pendings.length > 0) { - let one = socket.pendings.shift() - one!.copy(buffer, offset, one!.length) - offset += one!.length + const one = socket.pendings.shift() + offset += one!.copy(buffer, offset, one!.length) } return buffer @@ -157,18 +208,17 @@ function auth(_: WebSocket, uuid: string) { return uuid === UUID } -function tcp(socket: WebSocket, dest: Dest) { +function tcp(socket: WebSocket, dest: Dest, head: Buffer) { - if (!dest.host || !dest.port || dest.protocol != "tcp") { - console.error("cant socket to forward", dest.host, dest.port) - socket.close() - return - } + // console.log("connect to tcp", dest.host, dest.port) - const next = createConnection(dest) + const next = createConnection({ host: dest.host, port: dest.port }, () => { + console.log("tcp connected", socket.id, dest.host, dest.port) + }) next.setKeepAlive(true) next.setNoDelay(true) + next.setTimeout(3000) const stream = createWebSocketStream(socket, { allowHalfOpen: false, //可读端end的时候,调用可写端.end()了 @@ -178,10 +228,21 @@ function tcp(socket: WebSocket, dest: Dest) { writableObjectMode: false }) + if (head.length > 0) { + // console.log("send head", socket.id, dest.host, dest.port) + // console.log(head.toString("utf8")) + stream.unshift(head) + } + stream.pipe(next).pipe(stream) + next.on("error", (error) => { + console.error(socket.id, dest.host, dest.port, error) + next.destroySoon() + }) + const destroy = () => { - if (socket.OPEN) { + if (socket.readyState === WebSocket.OPEN) { socket.close() } @@ -193,41 +254,81 @@ function tcp(socket: WebSocket, dest: Dest) { finished(stream, destroy) } -function udp(socket: WebSocket, dest: Dest) { +function udp(socket: WebSocket, dest: Dest, head: Buffer) { - if (!dest.host || !dest.port || dest.protocol != "udp") { - console.error("cant socket to forward udp", dest.host, dest.port) - socket.close() - return + const waiting = { pendings: [] } as { pendings: Buffer[] } + + if (head.length > 0) { + waiting.pendings.push(head) + } + + let connected = false + const target = createSocket("udp4") + + function flushTarget() { + const buffer = fetch(waiting, 3) + if (buffer == null) { + return + } + const length = buffer.readUint16BE(0) + if (2 + length > buffer.length) { + waiting.pendings.push(buffer) + return + } + const end = 2 + length + target.send(buffer.subarray(2, end), dest.port, dest.host) + + if (end < buffer.length) { + waiting.pendings.push(buffer.subarray(end)) + flushTarget() + } } - const next = createSocket("udp4") + target.connect(dest.port, dest.host, () => { + connected = true + console.log("udp connected", dest.host, dest.port) + flushTarget() + }) + + target.on("message", (data) => { - next.connect(dest.port, dest.host) + //由于长度限制,这里要考虑分页 + let offset = 0 + const lenBuffer = BUFFER_LEN_RESERVE - next.on("message", (data) => { - socket.send(data) + //这里有问题,udp数据就应该完整弄过去的,但是uint16的长度有上限 + while (offset < data.length) { + const len = Math.min(data.length - offset, 65535) + lenBuffer.writeUint16BE(len) + socket.send(lenBuffer) + socket.send(data.subarray(offset, offset += len)) + } }) - next.on("error", () => { - next.close() - socket.close() + target.on("error", () => { + target.close() }) - next.on("close", () => { - socket.close() + target.once("close", () => { + connected = false + if (socket.readyState === WebSocket.OPEN) { + socket.close() + } }) socket.on("message", (data: Buffer) => { - next.send(data) + waiting.pendings.push(data) + if (connected) { + flushTarget() + } }) socket.on("error", () => { socket.close() }) - socket.on("close", () => { - next.close() + socket.once("close", () => { + target.close() }) } function mux(socket: WebSocket) { @@ -236,208 +337,232 @@ function mux(socket: WebSocket) { if (buffer == null) { return } - const meta_length = buffer.readUInt16BE() - if (meta_length < 4) { + let offset = 0 + const metaLength = buffer.readUInt16BE(offset) + + offset += 2 + + if (metaLength < 4) { socket.close() return } - if (2 + meta_length > buffer.length) { //没有收全 + if (offset + metaLength > buffer.length) { //没有收全 socket.pendings.push(buffer) return } - const meta = buffer.subarray(2, 2 + meta_length) - const type = meta[2] + const meta = buffer.subarray(offset, offset += metaLength) + const hasExtra = meta[3] == 1 - const has_extra = meta[3] == 1 + //额外数据开始的偏移 + const extra_length = hasExtra ? buffer.readUInt16BE(offset) : 0 - const extra_length_start = 2 + meta_length - const extra_length = has_extra ? buffer.readUInt16BE(extra_length_start) : 0 + offset += hasExtra ? 2 : 0 - if (has_extra && extra_length_start + 2 + extra_length > buffer.length) { + if (hasExtra && offset + extra_length > buffer.length) { //没有收全 socket.pendings.push(buffer) return } - let extra: Buffer | undefined - let left: Buffer | undefined + const extra = hasExtra ? buffer.subarray(offset, offset += extra_length) : undefined + const left = offset < buffer.length ? buffer.subarray(offset) : undefined - if (has_extra) { - const extra_start = extra_length_start + 2 - extra = buffer.subarray(extra_start, extra_start + extra_length) - left = buffer.subarray(extra_start + extra_length) + muxDispatch(socket, meta, extra) + + if (left && left.length > 0) { + // console.log("😈 recv mux left > 0", socket.id, type) + socket.pendings.push(left) + mux(socket) } - else { - left = buffer.subarray(2 + meta_length) +} + +function muxDispatch(socket: WebSocket, meta: Buffer, extra?: Buffer) { + + const uid = meta.readUInt16BE() + const cmd = meta[2] + + if (cmd === 1) { //创建 + muxNew(socket, uid, meta, extra) + return } - console.log("😈 recv mux cmd", socket.id, type) + const id = `${socket.id}/${uid}` + const session = sessions[id] - switch (type) { - case 1: //new - mux_new(socket, meta, extra) - break + if (!session) { + sendClientEnd(socket, meta) + return + } + + switch (cmd) { case 2: - mux_keep(socket, meta, extra) + muxKeep(socket, session, meta, extra) break case 3: - mux_end(socket, meta, extra) + muxEnd(socket, session, meta, extra) break case 4: - mux_keepalive(socket, meta, extra) + muxKeepAlive(socket, session, meta, extra) break default: socket.close() break } +} - if (left && left.length > 0) { - console.log("😈 recv mux left > 0", socket.id, type) - socket.pendings.push(left) - mux(socket) +function muxNew(socket: WebSocket, uid: number, meta: Buffer, extra?: Buffer) { + + const dest: Dest = readMetaAddress(meta) + const id = `${socket.id}/${uid}` + + if (!dest.host || dest.port === 0) { + sendClientEnd(socket, meta) + console.error("invalid mux new addressType:", dest.protocol, id, dest.host, dest.port) + return } -} -function mux_new(socket: WebSocket, meta: Buffer, extra?: Buffer) { + console.log("😈 mux new", dest.protocol, id, dest.host, dest.port) //@ts-ignore - const session: MuxSession = sessions[session.id] = { - id: meta.readUInt16BE(), - dest: { - //@ts-ignore - protocol: protocol_type_to_name[meta[4]!], - port: meta.readUInt16BE(5), - host: "", //Todo - } + const session: MuxSession = sessions[id] = { + id, + uid, + dest } - socket.on("error", () => { - socket.close() + switch (dest.protocol) { + case "tcp": + muxNewTcp(socket, session, meta) + break + case "udp": + muxNewUdp(socket, session, meta) + break + default: + socket.close() + return + } + + if (extra && extra.length > 0) { + muxKeep(socket, session, meta, extra) + } +} + +function muxNewTcp(socket: WebSocket, session: MuxSession, meta: Buffer) { + + const target = createConnection({ host: session.dest.host, port: session.dest.port }, () => { + console.log("mux tcp connected", session.id, session.dest.host, session.dest.port) }) - socket.on("close", () => { - delete sessions[session.id] - session.close() + target.setKeepAlive(true) + target.setNoDelay(true) + target.setTimeout(3000) + + target.on("data", (buffer: Buffer) => { + // console.log("-----------recv-----------") + // console.log(buffer.toString("utf8")) + + sendClientTcpKeep(socket, meta, buffer) + }) + target.on("end", () => { + target.destroy() }) - read_address(meta, session, 7) + target.on("error", () => { + target.destroy() + }) - if (!session.dest.host) { - socket.close() - console.error(new Error(`invalid addressType`)) - return - } + target.once("close", () => { + const deleted = delete sessions[session.id] + if (deleted) { + sendClientEnd(socket, meta) + } + }) - switch (session.dest.protocol) { - case "tcp": - { - const next = createConnection(session.dest) - - next.setKeepAlive(true) - next.setNoDelay(true) - - if (extra && extra.length > 0) { - socket.send(extra) - } - - next.on("message", (buffer: Buffer) => { - sendClientKeep(socket, meta, buffer) - }) - next.on("end", () => { - if (socket.OPEN) { - send_end_resp(socket, meta) - } - next.destroy() - }) - - next.on("error", () => { - next.destroy() - }) - - next.on("close", () => { - const deleted = delete sessions[session.id] - - if (deleted && socket.OPEN) { - send_end_resp(socket, meta) - } - }) - - session.send = (data: Buffer) => { - if (next.writable) { - next.write(data) - } - } - session.close = () => { - if (next.writable) { - next.destroy() - } - } - } - break - case "udp": - { - const next = createSocket("udp4") - - next.connect(session.dest.port, session.dest.host) - - next.on("message", (data) => { - sendClientKeep(socket,meta,data) - }) - - next.on("error", () => { - next.close() - socket.close() - }) - - next.on("close", () => { - const deleted = delete sessions[session.id] - - if (deleted && socket.OPEN) { - send_end_resp(socket, meta) - } - }) - - session.send = (data: Buffer) => { - next.send(data) - } - - session.close = () => { - next.disconnect() - } - } + session.send = (data: Buffer) => { + if (target.writable) { + target.write(data) + } + } + session.close = () => { + if (target.writable) { + target.destroySoon() + } } } -function mux_keep(socket: WebSocket, meta: Buffer, extra?: Buffer) { +function muxNewUdp(socket: WebSocket, session: MuxSession, meta: Buffer) { - const id = meta.readUInt16BE().toString() - const session = sessions[id] + let alreadyClose = false + let last = Date.now() - if (!session) { - send_end_resp(socket, meta) - return - } - if (!extra || extra.length == 0) { - return + const target = createSocket("udp4") + + target.bind() + target.on("message", (data, rinfo: RemoteInfo) => { + last = Date.now() + + // console.log("send client mux keep udp", session.id, rinfo.address, rinfo.port) + sendClientUdpKeep(socket, meta, rinfo, data) + }) + + target.on("error", () => { + target.close() + }) + + const timer = setInterval(() => { + if (Date.now() - last < 30000) { + return + } + if (!alreadyClose) { + target.close() + } + }, 10000) + + target.once("close", () => { + alreadyClose = true + clearInterval(timer) + const deleted = delete sessions[session.id] + if (deleted) { + sendClientEnd(socket, meta) + } + }) + + session.send = (msg: Buffer, port: number, host: string) => { + last = Date.now() + //@ts-ignore + target.send(msg, port, host) } - session.send(extra) + session.close = () => { + if (!alreadyClose) { + target.close() + } + } } -function mux_end(socket: WebSocket, meta: Buffer, extra?: Buffer) { +function muxKeep(socket: WebSocket, session: MuxSession, meta: Buffer, extra?: Buffer) { - const id = meta.readUInt16BE().toString() + if (!extra || extra.length == 0) { + return + } - const session = sessions[id] - if (session == null) { + if (session.dest.protocol === "tcp") { + session.send(extra) return } - delete sessions[id] + const dest = readMetaAddress(meta) + + session.send(extra, dest.port, dest.host) +} + +function muxEnd(socket: WebSocket, session: MuxSession, meta: Buffer, extra?: Buffer) { - console.log("mux end", socket.id, id, session.dest.host) + delete sessions[session.id] + + console.log("mux end", session.dest.protocol, session.id, session.dest.host, session.dest.port) if (extra) { session.send(extra) @@ -445,39 +570,123 @@ function mux_end(socket: WebSocket, meta: Buffer, extra?: Buffer) { session.close() } -function mux_keepalive(socket: WebSocket, meta: Buffer, extra?: Buffer) { } +function muxKeepAlive(socket: WebSocket, session: MuxSession, meta: Buffer, extra?: Buffer) { + + /** + * 保持连接 (KeepAlive) + 2 字节 1 字节 1 字节 + ID 0x04 选项 Opt + 在保持连接时: + + 若 Opt(D) 开启,则这一帧所带的数据必须被丢弃。 + ID 可为随机值。 + #应用 + */ + + const id = `${socket.id}/${meta.readUInt16BE()}` + + console.log("mux keepAlive", id) +} + +function sendClientTcpKeep(socket: WebSocket, originMeta: Buffer, extra: Buffer) { -function sendClientKeep(socket: WebSocket, originMeta: Buffer, extra: Buffer) { + if (socket.readyState !== WebSocket.OPEN) { + return + } const meta = originMeta.subarray(0, 4) - meta[2] = 2 - meta[3] = 1 + //[0][1] = id + meta[2] = 2 //cmd + meta[3] = 1 //hasExtra + + if (extra.length < 65535) { + sendClientMuxData(socket, meta, extra) + return + } + + //由于长度限制,这里要考虑分页 + let offset = 0 + while (offset < extra.length) { + const len = Math.min(extra.length - offset, 65535) + sendClientMuxData(socket, meta, extra.subarray(offset, offset += len)) + } +} - const resp = Buffer.alloc(2 + meta.length + 2) +function sendClientUdpKeep(socket: WebSocket, originMeta: Buffer, rinfo: RemoteInfo, extra: Buffer) { + + if (socket.readyState !== WebSocket.OPEN) { + return + } - resp.writeUint16BE(meta.length) - meta.copy(resp, 2) + const preparedMeta = BUFFER_META_RESERVE - resp.writeUint16BE(extra.length, meta.length + 2) + //id + preparedMeta[0] = originMeta[0]! + preparedMeta[1] = originMeta[1]! - socket.send(resp) - socket.send(extra) + preparedMeta[2] = 2 //cmd + preparedMeta[3] = 1 //hasExtra + + const dest: Dest = { + port: rinfo.port, + host: rinfo.address, + protocol: "udp", + //@ts-ignore + family: rinfo.family.toLowerCase(), + } + + const metaLength = writeMetaAddress(preparedMeta, dest, 4) + const meta = preparedMeta.subarray(0, metaLength) + + if (extra.length < 65535) { + sendClientMuxData(socket, meta, extra) + return + } + + //由于长度限制,这里要考虑分页 + let offset = 0 + while (offset < extra.length) { + const len = Math.min(extra.length - offset, 65535) + sendClientMuxData(socket, meta, extra.subarray(offset, offset += len)) + } } -function send_end_resp(socket: WebSocket, originMeta: Buffer) { +function sendClientMuxData(socket: WebSocket, meta: Buffer, data: Buffer) { + + //meta.length(2) + meta(meta.length) + data.length(2) + data(data.length) + + const lenBuffer = BUFFER_LEN_RESERVE - console.log("😢 send mux end", socket.id, socket.id) + lenBuffer.writeUint16BE(meta.length) + + socket.send(lenBuffer) + socket.send(meta) + + lenBuffer.writeUint16BE(data.length) + + socket.send(lenBuffer) + socket.send(data) +} + +function sendClientEnd(socket: WebSocket, originMeta: Buffer) { + + if (socket.readyState !== WebSocket.OPEN) { + return + } + + // console.log("😢 send mux end", id) const meta = originMeta.subarray(0, 4) meta[2] = 3 //type meta[3] = 0 //has_opt - const resp = Buffer.allocUnsafe(2) - resp.writeUint16BE(meta.length) + const lenBuffer = BUFFER_LEN_RESERVE + + lenBuffer.writeUint16BE(meta.length) - socket.send(resp) + socket.send(lenBuffer) socket.send(meta) } diff --git a/src/types.ts b/src/types.ts index 1a236de..51d9fde 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,26 +1,49 @@ -import { WebSocket } from "ws"; -import { Socket as TcpSocket } from "net"; +import { WebSocket } from "ws"; +import { Socket as TcpSocket } from "net"; import { Socket as UdpSocket } from "dgram"; declare module 'ws' { interface WebSocket { id: number; - pendings:Array; - next:Function; + pendings: Array; + next: Function; } } -export interface Dest -{ + +// export type VlessProtocol = "tcp" | "udp" | "mux" | "unknown" + +export const Protocols = { + tcp: 1, + udp: 2, + mux: 3, +} + + +export const NameProtocols = Object.entries(Protocols).reduce((acc, [key, value]) => { + //@ts-ignore + acc[value] = key; + return acc; +}, {} as Record) + +export type Protocol = keyof typeof Protocols; +export type ProtocolValue = typeof Protocols[Protocol]; + +export interface Dest { port: number; host: string; - protocol: "tcp" | "udp" | "unknown"; + protocol: Protocol | undefined; + family: "ipv4" | "ipv6" | "domain"; } export type MuxSession = { - id:number; - dest:Dest; - send:(...args:any[])=>void; - close:()=>void; + id: string; + uid: number; + dest: Dest; + send: (...args: any[]) => void; + close: () => void; } + + + diff --git a/src/utils.ts b/src/utils.ts index d550f4d..21f9d3c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,111 +1,93 @@ -const Common_AddressType = -{ - [1]: "IPv4", - [2]: "domain", - [3]: "IPv6" -} +import { Dest, Protocols, NameProtocols } from "./types" -const Common_AddressType_Value = { - IPv4: 1, - domain: 2, - IPv6: 3 -} +export const isIPv4 = (address: string) => /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(address) +export const isIPv6 = (address: string) => /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/.test(address) +export const isDomain = (address: string) => /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9])*$/.test(address) + +export function readMetaAddress(meta: Buffer, offset: number = 4) { -/** - * addresstype: 01-->ipv4,02-->domain,03-->ipv6 - * socks5:IPV4: 0x01,domain:03,ipv6:04 - * @param buffer - * @param offset - * @param address - * @returns - */ -export function read_address(buffer: Buffer, address: any, offset: number = 0) { - - const names = Common_AddressType - const address_type = buffer[offset++] //@ts-ignore - const name = names[address_type!] + const dest: Dest = {} - switch (name) { - case "IPv4": //ipv4 - { - address.family = "IPv4" - address.host = `${buffer[offset++]}.${buffer[offset++]}.${buffer[offset++]}.${buffer[offset++]}` - } + dest.protocol = NameProtocols[meta.readUint8(offset++)] + dest.port = meta.readUInt16BE(offset) + + offset += 2 + + readAddress(meta, dest, offset) + + return dest +} + +export function readAddress(meta: Buffer, dest: Dest, offset: number) { + + const addressType = meta.readUint8(offset++) + + switch (addressType) { + case 0x01: //ipv4 + dest.family = "ipv4" + dest.host = `${meta[offset++]}.${meta[offset++]}.${meta[offset++]}.${meta[offset++]}` break - case "domain": //domain + case 0x02: //domain { - const size = buffer[offset++] - address.host = buffer.subarray(offset, offset += size!).toString() + const size = meta[offset++] + dest.family = "domain" + dest.host = meta.subarray(offset, offset += size!).toString() } break - case "IPv6": //ipv6 + case 0x03: //ipv6 { const array = [] - for (let i = 0; i < 8; i++) { - array.push(buffer.readUint16BE(offset).toString(16)); - offset += 2 + for (let i = 0; i < 8; i++, offset += 2) { + array.push(meta.readUint16BE(offset).toString(16)); } - address.family = "IPv6" - address.host = array.join(":") + dest.family = "ipv6" + dest.host = array.join(":") } break - default: - address.host = null - break } - address.address = address.host - return offset } -export function write_address(buffer: Buffer, address: any, offset: number = 0) { - - const address_type_pos = offset - const names = Common_AddressType_Value +export function writeMetaAddress(meta: Buffer, dest: Dest, offset: number) { - buffer[offset++] = 1 + offset = meta.writeUint8(Protocols[dest.protocol!], offset) + offset = meta.writeUInt16BE(dest.port, offset) - switch (address.family) { - case "IPv4": - { - const array = (address.address || address.host).split(".") + return writeAddress(meta, dest, offset) +} - for (let i = 0; i < 4; ++i) { - const val = parseInt(array[i]) - buffer[offset++] = val - } +export function writeAddress(meta: Buffer, dest: Dest, offset: number) { - buffer[address_type_pos] = names["IPv4"] - } + switch (dest.family) { + case "ipv4": + offset = meta.writeUInt8(0x01, offset) + dest.host.split(".").forEach(e => offset = meta.writeUInt8(parseInt(e), offset)) break - case "IPv6": + case "domain": { - const array = (address.address || address.host).split(":") + const sub = Buffer.from(dest.host) - for (let i = 0; i < 8; ++i) { - const val = parseInt(array[i], 16) - buffer.writeUint16BE(val, offset) - offset += 2 - } + offset = meta.writeUInt8(0x02, offset) + offset = meta.writeUInt8(sub.byteLength, offset) - buffer[address_type_pos] = names["IPv6"] + offset += sub.copy(meta, offset) } break - default: //Domain + case "ipv6": { - const sub = Buffer.from((address.address || address.host)) - - buffer[offset++] = sub.byteLength + const array = dest.host.split(":") - sub.copy(buffer, offset, 0, offset += sub.byteLength) + offset = meta.writeUInt8(0x03, offset) - buffer[address_type_pos] = names["domain"] + for (let i = 0; i < array.length; i++) { + offset = meta.writeUInt16BE(parseInt(array[i]!, 16), offset) + } } break } return offset -} \ No newline at end of file +}