add node vless (#95)

add node vless
This commit is contained in:
zizifn3 2023-01-19 23:50:16 +08:00 committed by GitHub
parent 26a65b5778
commit 620c57e602
27 changed files with 602 additions and 27 deletions

View File

@ -4,6 +4,7 @@
"deno.enablePaths": [
"apps/deno-bypass",
"apps/deno-vless/src/deno",
"apps/deno-vless/src/main.ts"
"apps/deno-vless/src/main.ts",
"apps/deno-vless/src/deno-test.ts"
]
}

View File

@ -42,15 +42,14 @@ https://blog.cloudflare.com/introducing-socket-workers/
## V2ray Edge server --- Node.js
很多 Node.js 的平台都是支持 docker 的,所以可以直接部署原版。但是既然很多人要,我就写一个,但是我不承若一定回答关于 Node.js 平台的所有问题。因为太多了。
### railway.app
很多 Node.js 的平台都是支持 docker 的,所以可以直接部署原版。但是既然很多人要,我就写一个。我目前仅仅 render 平台。
### render.com
## 客户端 v2rayN 配置
> ⚠️ 由于 edge 平台限制,无法转发 UDP 包。请在配置时候,把 DNS 的策略改成 "Asis", 否则会影响速度。
> 请不要开启 ipv6 优先。
> [ DNS 科普文章](https://tachyondevel.medium.com/%E6%BC%AB%E8%B0%88%E5%90%84%E7%A7%8D%E9%BB%91%E7%A7%91%E6%8A%80%E5%BC%8F-dns-%E6%8A%80%E6%9C%AF%E5%9C%A8%E4%BB%A3%E7%90%86%E7%8E%AF%E5%A2%83%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8-62c50e58cbd0)

View File

@ -0,0 +1,16 @@
import { serve } from 'https://deno.land/std@0.170.0/http/server.ts';
const handler = async (req: Request) => {
console.log('start');
const connect = await Deno.connect({
port: 443,
hostname: '2606:4700:0000:0000:0000:0000:6810:7c60',
});
console.log(connect.remoteAddr);
return new Response('hello', {
status: 200,
});
};
serve(handler, { port: 8081, hostname: '0.0.0.0' });

View File

@ -29,7 +29,7 @@ const handler = async (req: Request): Promise<Response> => {
const { socket, response } = Deno.upgradeWebSocket(req);
socket.addEventListener('open', () => {});
let test: Deno.TcpConn | null = null;
// let test: Deno.TcpConn | null = null;
// test!.writable.abort();
//
processWebSocket({

View File

@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,16 @@
/* eslint-disable */
export default {
displayName: 'node-vless',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/node-vless',
};

View File

@ -0,0 +1,61 @@
{
"name": "node-vless",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/node-vless/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/apps/node-vless",
"main": "apps/node-vless/src/main.ts",
"tsConfig": "apps/node-vless/tsconfig.app.json",
"assets": ["apps/node-vless/src/assets"]
},
"configurations": {
"production": {
"optimization": true,
"extractLicenses": true,
"inspect": false,
"fileReplacements": [
{
"replace": "apps/node-vless/src/environments/environment.ts",
"with": "apps/node-vless/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/js:node",
"options": {
"buildTarget": "node-vless:build"
},
"configurations": {
"production": {
"buildTarget": "node-vless:build:production"
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/node-vless/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/node-vless/jest.config.ts",
"passWithNoTests": true
}
}
},
"implicitDependencies": ["cf-page"],
"tags": []
}

View File

View File

@ -0,0 +1,61 @@
import { createReadStream, existsSync } from 'node:fs';
import { IncomingMessage, ServerResponse } from 'node:http';
import { resolve, join, extname } from 'node:path';
import { cacheHeader } from 'pretty-cache-header';
const mimeLookup = {
'.js': 'application/javascript,charset=UTF-8',
'.html': 'text/html,charset=UTF-8',
'.css': 'text/css; charset=UTF-8',
};
const staticPath = 'dist/apps/cf-page/';
const file401 = 'dist/apps/node-vless/assets/401.html';
let filepath = null;
export function serverStaticFile(req: IncomingMessage, resp: ServerResponse) {
const url = new URL(req.url, `http://${req.headers['host']}`);
let fileurl = url.pathname;
fileurl = join(staticPath, fileurl);
console.log('....', fileurl);
filepath = resolve(fileurl);
console.log(filepath);
if (existsSync(filepath)) {
let fileExt = extname(filepath);
console.log('fileExt', fileExt);
let mimeType = mimeLookup[fileExt];
resp.writeHead(200, {
'Content-Type': mimeType,
'Cache-Control': cacheHeader({
public: true,
maxAge: '1year',
staleWhileRevalidate: '1year',
}),
});
return createReadStream(filepath).pipe(resp);
} else {
resp.writeHead(404);
resp.write('not found');
resp.end();
return resp;
}
}
export function index401(req: IncomingMessage, resp: ServerResponse) {
const file401Path = resolve(file401);
if (existsSync(file401Path)) {
createReadStream(file401Path).pipe(resp);
} else {
resp.writeHead(401);
resp.write('UUID env not set');
resp.end();
}
}
export function serverIndexPage(
req: IncomingMessage,
resp: ServerResponse,
uuid
) {
// if()
}

View File

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>401 - UUID Not Valid</title>
</head>
<body>
<h1 style="color: red;">Not set valid UUID in Environment Variables.</h1>
<h2>Please use tool to generate and <span style="color: red;">remember</span> UUID or use this one <span
style="color: blue;" id="uuidSpan"></span>
</h2>
<h3> You must use same UUID for login this page after config valid UUID Environment Variables
</h3>
<h2>Please refer to <a
href="https://github.com/zizifn/edgetunnel/blob/main/doc/edge-tunnel-deno.md#%E6%B5%81%E7%A8%8B%E6%BC%94%E7%A4%BA">deno
deploy guide</a>
</h2>
<h3>Or maybe check below <a
href="https://raw.githubusercontent.com/zizifn/edgetunnel/main/doc/deno-deploy2.gif">GIF</a> </h3>
<img src="https://raw.githubusercontent.com/zizifn/edgetunnel/main/doc/deno-deploy2.gif" alt="guide" srcset="">
<script>
let uuid = URL.createObjectURL(new Blob([])).substr(-36);
document.getElementById('uuidSpan').textContent = uuid
</script>
</body>
</html>

View File

@ -0,0 +1,3 @@
export const environment = {
production: true,
};

View File

@ -0,0 +1,3 @@
export const environment = {
production: false,
};

219
apps/node-vless/src/main.ts Normal file
View File

@ -0,0 +1,219 @@
import { createServer } from 'http';
import { parse } from 'url';
import { WebSocketServer, WebSocket } from 'ws';
import { index401, serverStaticFile } from './app/utils';
import * as uuid from 'uuid';
import * as lodash from 'lodash';
import { createReadStream } from 'node:fs';
import {
makeReadableWebSocketStream,
processVlessHeader,
delay,
closeWebSocket,
} from 'vless-js';
import { connect, Socket } from 'node:net';
import { Duplex, Readable } from 'stream';
import { resolve } from 'path';
const port = process.env.PORT;
const userID = process.env.UUID || '';
let isVaildUser = uuid.validate(userID);
if (!isVaildUser) {
console.log('not set valid UUID');
}
const server = createServer((req, resp) => {
if (!isVaildUser) {
return index401(req, resp);
}
const url = new URL(req.url, `http://${req.headers['host']}`);
// health check
if (req.method === 'GET' && url.pathname.startsWith('/health')) {
resp.writeHead(200);
resp.write('health 200');
resp.end();
return;
}
// index page
if (url.pathname.includes(userID)) {
const index = 'dist/apps/cf-page/index.html';
return createReadStream(index).pipe(resp);
}
if (req.method === 'GET' && url.pathname.startsWith('/assets')) {
return serverStaticFile(req, resp);
}
const basicAuth = req.headers.authorization || '';
const authStringBase64 = basicAuth.split(' ')?.[1] || '';
const authString = Buffer.from(authStringBase64, 'base64').toString('ascii');
console.log('-----authString--', authString);
if (authString && authString.includes(userID)) {
resp.writeHead(302, {
'content-type': 'text/html; charset=utf-8',
Location: `./${userID}`,
});
resp.end();
} else {
resp.writeHead(401, {
'content-type': 'text/html; charset=utf-8',
'WWW-Authenticate': 'Basic',
});
resp.end();
}
});
const vlessWServer = new WebSocketServer({ noServer: true });
vlessWServer.on('connection', async function connection(ws) {
let address = '';
let portWithRandomLog = '';
try {
const log = (info: string, event?: any) => {
console.log(`[${address}:${portWithRandomLog}] ${info}`, event || '');
};
let remoteConnection: Socket = null;
let remoteConnectionReadyResolve: Function;
const readableWebSocketStream = makeReadableWebSocketStream(ws, log);
let vlessResponseHeader: Uint8Array | null = null;
// ws --> remote
readableWebSocketStream
.pipeTo(
new WritableStream({
async write(chunk: Buffer, controller) {
const vlessBuffer = chunk.buffer.slice(chunk.byteOffset);
if (remoteConnection) {
await wsAsyncWrite(remoteConnection, vlessBuffer);
return;
}
const {
hasError,
message,
portRemote,
addressRemote,
rawDataIndex,
vlessVersion,
} = processVlessHeader(vlessBuffer, userID, uuid, lodash);
address = addressRemote || '';
portWithRandomLog = `${portRemote}--${Math.random()}`;
if (hasError) {
controller.error(`[${address}:${portWithRandomLog}] ${message} `);
}
// const addressType = requestAddr >> 42
// const addressLength = requestAddr & 0x0f;
console.log(`[${address}:${portWithRandomLog}] connecting`);
remoteConnection = await connect2Remote(portRemote, address, log);
vlessResponseHeader = new Uint8Array([vlessVersion![0], 0]);
const rawClientData = vlessBuffer.slice(rawDataIndex!);
remoteConnection.write(new Uint8Array(rawClientData));
remoteConnectionReadyResolve(remoteConnection);
},
close() {
console.log(
`[${address}:${portWithRandomLog}] readableWebSocketStream is close`
);
},
abort(reason) {
console.log(
`[${address}:${portWithRandomLog}] readableWebSocketStream is abort`,
JSON.stringify(reason)
);
},
})
)
.catch((error) => {
console.error(
`[${address}:${portWithRandomLog}] readableWebSocketStream pipeto has exception`,
error.stack || error
);
// error is cancel readable stream anyway, no need close websocket in here
// closeWebSocket(webSocket);
// close remote conn
// remoteConnection?.close();
});
await new Promise((resolve) => (remoteConnectionReadyResolve = resolve));
// remote --> ws
let remoteChunkCount = 0;
let totoal = 0;
await Readable.toWeb(remoteConnection).pipeTo(
new WritableStream({
start() {
if (ws.readyState === ws.OPEN) {
ws.send(vlessResponseHeader!);
}
},
async write(chunk: Uint8Array, controller) {
ws.send(chunk);
},
close() {
console.log(
`[${address}:${portWithRandomLog}] remoteConnection!.readable is close`
);
},
abort(reason) {
closeWebSocket(ws);
console.error(
`[${address}:${portWithRandomLog}] remoteConnection!.readable abort`,
reason
);
},
})
);
} catch (error) {
console.error(
`[${address}:${portWithRandomLog}] processWebSocket has exception `,
error.stack || error
);
closeWebSocket(ws);
}
});
server.on('upgrade', function upgrade(request, socket, head) {
console.log('upgrade');
const { pathname } = parse(request.url);
if (pathname === '/foo') {
vlessWServer.handleUpgrade(request, socket, head, function done(ws) {
vlessWServer.emit('connection', ws, request);
});
} else {
socket.destroy();
}
});
server.listen(port, () => {
console.log(`server listen in http://127.0.0.1:${port}`);
});
async function connect2Remote(port, host, log: Function): Promise<Socket> {
return new Promise((resole, reject) => {
const remoteSocket = connect(
{
port: port,
host: host,
},
() => {
log(`connected`);
resole(remoteSocket);
}
);
remoteSocket.addListener('error', () => {
reject('remoteSocket has error');
});
});
}
async function wsAsyncWrite(ws: Socket, chunk: ArrayBuffer) {
return new Promise((resolve, reject) => {
ws.write(Buffer.from(chunk), (error) => {
if (error) {
reject(error);
} else {
resolve('');
}
});
});
}

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["node"]
},
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"],
"include": ["**/*.ts"]
}

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
}

29
doc/render.md Normal file
View File

@ -0,0 +1,29 @@
# Render
## 登录 render 账户
https://render.com/
## 访问 https://dashboard.render.com/
## New Project
![render1](./render1.jpg)
## 关联 github 账户
![render2](./render2.jpg)
## 部署新项目
需要填写如下信息,具体请参考下图.
| 选项 | 值 |
| ------------- | --- |
| Build Command | 3 |
| Start Command | 3 |
![render3](./render3.jpg)
**⚠️ 添加环境变量 UUID**
![render4](./render4.jpg)

BIN
doc/render1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
doc/render2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
doc/render3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

BIN
doc/render4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1 +1 @@
export { vlessJs, processWebSocket as processSocket } from './lib/vless-js';
export * from './lib/vless-js';

View File

@ -2,7 +2,7 @@ export function vlessJs(): string {
return 'vless-js';
}
function delay(ms: number) {
export function delay(ms: number) {
return new Promise((resolve, rej) => {
setTimeout(resolve, ms);
});
@ -163,16 +163,22 @@ export async function processWebSocket({
return;
}
function makeReadableWebSocketStream(ws: WebSocket, log: Function) {
export function makeReadableWebSocketStream(
ws: WebSocket | any,
log: Function
) {
let readableStreamCancel = false;
return new ReadableStream<ArrayBuffer>({
start(controller) {
ws.addEventListener('message', async (e) => {
ws.addEventListener('message', async (e: { data: ArrayBuffer }) => {
// console.log('MESSAGE');
const vlessBuffer: ArrayBuffer = e.data;
// console.log('MESSAGE', vlessBuffer);
// console.log(`message is ${vlessBuffer.byteLength}`);
controller.enqueue(vlessBuffer);
});
ws.addEventListener('error', (e) => {
ws.addEventListener('error', (e: any) => {
log('socket has error');
readableStreamCancel = true;
controller.error(e);
@ -202,13 +208,19 @@ function makeReadableWebSocketStream(ws: WebSocket, log: Function) {
});
}
function closeWebSocket(socket: WebSocket) {
export function closeWebSocket(socket: WebSocket | any) {
if (socket.readyState === socket.OPEN) {
socket.close();
}
}
function processVlessHeader(
//https://github.com/v2ray/v2ray-core/issues/2636
// 1 字节 16 字节 1 字节 M 字节 1 字节 2 字节 1 字节 S 字节 X 字节
// 协议版本 等价 UUID 附加信息长度 M 附加信息 ProtoBuf 指令 端口 地址类型 地址 请求数据
// 1 字节 1 字节 N 字节 Y 字节
// 协议版本,与请求的一致 附加信息长度 N 附加信息 ProtoBuf 响应数据
export function processVlessHeader(
vlessBuffer: ArrayBuffer,
userID: string,
uuidLib: any,

19
node-test.mjs Normal file
View File

@ -0,0 +1,19 @@
import { connect, Socket } from 'node:net';
import { Duplex } from 'node:stream';
import { WritableStream } from 'node:stream/web';
try {
const socket = connect(
{
port: '443',
host: 'www.google.com',
},
() => {
console.log('connect ', socket.readyState);
}
);
} catch (err) {
console.log('----', err);
}
console.log('end');

72
package-lock.json generated
View File

@ -13,6 +13,8 @@
"@heroicons/react": "^2.0.13",
"commander": "^9.4.1",
"core-js": "^3.6.5",
"lodash": "^4.17.21",
"pretty-cache-header": "^1.0.0",
"qrcode": "^1.5.1",
"react": "18.2.0",
"react-dom": "18.2.0",
@ -20,7 +22,8 @@
"tslib": "^2.3.0",
"undici": "^5.13.0",
"uuid": "^9.0.0",
"wrangler": "^2.6.2"
"wrangler": "^2.6.2",
"ws": "^8.12.0"
},
"devDependencies": {
"@babel/preset-react": "^7.14.5",
@ -39,6 +42,7 @@
"@nrwl/workspace": "15.2.4",
"@testing-library/react": "13.4.0",
"@types/jest": "28.1.1",
"@types/lodash": "^4.14.191",
"@types/node": "18.11.9",
"@types/qrcode": "^1.5.0",
"@types/react": "18.0.25",
@ -5857,6 +5861,12 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.191",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.191.tgz",
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"node_modules/@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/@types/mime/-/mime-3.0.1.tgz",
@ -13487,8 +13497,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
@ -15896,6 +15905,17 @@
"node": ">=4"
}
},
"node_modules/pretty-cache-header": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/pretty-cache-header/-/pretty-cache-header-1.0.0.tgz",
"integrity": "sha512-xtXazslu25CdnGnUkByU1RoOjK55TqwatJkjjJLg5ZAdz2Lngko/mmaUgeET36P2GMlNwh3fdM7FWBO717pNcw==",
"dependencies": {
"timestring": "^6.0.0"
},
"engines": {
"node": ">=12.13"
}
},
"node_modules/pretty-format": {
"version": "28.1.3",
"resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-28.1.3.tgz",
@ -18099,6 +18119,14 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
"node_modules/timestring": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/timestring/-/timestring-6.0.0.tgz",
"integrity": "sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==",
"engines": {
"node": ">=8"
}
},
"node_modules/tinybench": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.3.1.tgz",
@ -19759,16 +19787,15 @@
}
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"dev": true,
"version": "8.12.0",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.12.0.tgz",
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
@ -24444,6 +24471,12 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"@types/lodash": {
"version": "4.14.191",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.191.tgz",
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/@types/mime/-/mime-3.0.1.tgz",
@ -30522,8 +30555,7 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.camelcase": {
"version": "4.3.0",
@ -32325,6 +32357,14 @@
}
}
},
"pretty-cache-header": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/pretty-cache-header/-/pretty-cache-header-1.0.0.tgz",
"integrity": "sha512-xtXazslu25CdnGnUkByU1RoOjK55TqwatJkjjJLg5ZAdz2Lngko/mmaUgeET36P2GMlNwh3fdM7FWBO717pNcw==",
"requires": {
"timestring": "^6.0.0"
}
},
"pretty-format": {
"version": "28.1.3",
"resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-28.1.3.tgz",
@ -34134,6 +34174,11 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
"timestring": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/timestring/-/timestring-6.0.0.tgz",
"integrity": "sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA=="
},
"tinybench": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.3.1.tgz",
@ -35249,10 +35294,9 @@
}
},
"ws": {
"version": "8.11.0",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"dev": true,
"version": "8.12.0",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.12.0.tgz",
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
"requires": {}
},
"xml-name-validator": {

View File

@ -2,10 +2,15 @@
"name": "edge-bypass",
"version": "0.0.0",
"license": "MIT",
"engines": {
"node": "^18"
},
"scripts": {
"start": "nx serve",
"build": "nx build",
"cf-page": "nx build cf-page",
"node-vless:build": "nx build cf-page --configuration=production && nx build node-vless",
"node-vless:start": "node dist/apps/node-vless/main.js",
"test": "nx test"
},
"private": true,
@ -14,6 +19,8 @@
"@heroicons/react": "^2.0.13",
"commander": "^9.4.1",
"core-js": "^3.6.5",
"lodash": "^4.17.21",
"pretty-cache-header": "^1.0.0",
"qrcode": "^1.5.1",
"react": "18.2.0",
"react-dom": "18.2.0",
@ -21,7 +28,8 @@
"tslib": "^2.3.0",
"undici": "^5.13.0",
"uuid": "^9.0.0",
"wrangler": "^2.6.2"
"wrangler": "^2.6.2",
"ws": "^8.12.0"
},
"devDependencies": {
"@babel/preset-react": "^7.14.5",
@ -40,6 +48,7 @@
"@nrwl/workspace": "15.2.4",
"@testing-library/react": "13.4.0",
"@types/jest": "28.1.1",
"@types/lodash": "^4.14.191",
"@types/node": "18.11.9",
"@types/qrcode": "^1.5.0",
"@types/react": "18.0.25",