diff --git a/examples/add-client.ts b/examples/add-client.ts deleted file mode 100644 index 0fefd3f..0000000 --- a/examples/add-client.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Panel } from "3x-ui"; - -const local = new Panel("http://username:password@localhost:2053"); - -const inboundId = 1; -await local.addClient(inboundId, { - alterId: 0, - email: "email", - enable: true, - expiryTime: 1682864675944, - id: "95e4e7bb-7796-47e7-e8a7-f4055194f776", - limitIp: 2, - subId: "", - tgId: "", - totalGB: 42949672960, -}); diff --git a/examples/add-inbound.ts b/examples/add-inbound.ts deleted file mode 100644 index b48b61d..0000000 --- a/examples/add-inbound.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Panel } from "3x-ui"; - -const local = new Panel("http://username:password@localhost:2053"); -const inbound = await local.addInbound({ - enable: true, - remark: "New inbound", - listen: "", - port: 48965, - protocol: "vmess", - expiryTime: 0, - settings: JSON.stringify({ - clients: [], - decryption: "none", - fallbacks: [], - }), - streamSettings: JSON.stringify({ - network: "ws", - security: "none", - wsSettings: { - acceptProxyProtocol: false, - path: "/", - headers: {}, - }, - }), - sniffing: JSON.stringify({ - enabled: true, - destOverride: ["http", "tls"], - }), -}); - -// { -// id: 1, -// up: 0, -// down: 0, -// total: 0, -// remark: "New inbound", -// enable: true, -// expiryTime: 0, -// clientStats: null, -// listen: "", -// port: 48965, -// protocol: "vmess", -// settings: -// '{"clients": [ { "alterId": 0, "email": "xn1aaiwm", "enable": true, "expiryTime": 0, "id": "1db9f8ba-d1ad-4b0e-cea2-6edf9947dae5", "limitIp": 0, "subId": "", "tgId": "", "totalGB": 0 }],"decryption": "none","fallbacks": []\n}', -// streamSettings: -// '{"network":"ws","security":"none","wsSettings":{"acceptProxyProtocol":false,"path":"/","headers":{}}}', -// tag: "inbound-48965", -// sniffing: '{"enabled":true,"destOverride":["http","tls"]}', -// } diff --git a/examples/get-client-options.ts b/examples/get-client-options.ts deleted file mode 100644 index 85300dd..0000000 --- a/examples/get-client-options.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Panel } from "3x-ui"; - -const local = new Panel("http://username:password@localhost:2053"); - -const clientByEmail = local.getClientOptions("email"); - -// for vmess, vless protocols -const clientByUUID = local.getClientOptions("95e4e7bb-7796-47e7-e8a7-f4055194f776"); - -// for trojan protocol -const clientByPassword = local.getClientOptions("password"); - -// { -// id: "95e4e7bb-7796-47e7-e8a7-f4055194f776", -// alterId: 0, -// email: "email", -// limitIp: 2, -// totalGB: 42949672960, -// expiryTime: 1682864675944, -// enable: true, -// tgId: "", -// subId: "", -// } diff --git a/examples/get-client.ts b/examples/get-client.ts deleted file mode 100644 index 5f79943..0000000 --- a/examples/get-client.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Panel } from "3x-ui"; - -const local = new Panel("http://username:password@localhost:2053"); - -const clientByEmail = local.getClient("email"); - -// for vmess, vless protocols -const clientByUUID = local.getClientByUUID("95e4e7bb-7796-47e7-e8a7-f4055194f776"); - -// for trojan protocol -const clientByPassword = local.getClientByPassword("password"); diff --git a/examples/get-inbound.ts b/examples/get-inbound.ts deleted file mode 100644 index 22a8baf..0000000 --- a/examples/get-inbound.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Panel } from "3x-ui"; - -const local = new Panel("http://username:password@localhost:2053"); -const inbound = local.getInbound(1); - -// { -// id: 1, -// up: 0, -// down: 0, -// total: 0, -// remark: "New inbound", -// enable: true, -// expiryTime: 0, -// clientStats: null, -// listen: "", -// port: 48965, -// protocol: "vmess", -// settings: -// '{"clients": [ { "alterId": 0, "email": "xn1aaiwm", "enable": true, "expiryTime": 0, "id": "1db9f8ba-d1ad-4b0e-cea2-6edf9947dae5", "limitIp": 0, "subId": "", "tgId": "", "totalGB": 0 }],"decryption": "none","fallbacks": []\n}', -// streamSettings: -// '{"network":"ws","security":"none","wsSettings":{"acceptProxyProtocol":false,"path":"/","headers":{}}}', -// tag: "inbound-48965", -// sniffing: '{"enabled":true,"destOverride":["http","tls"]}', -// } diff --git a/examples/update-client.ts b/examples/update-client.ts deleted file mode 100644 index 048233d..0000000 --- a/examples/update-client.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Panel } from "3x-ui"; - -const local = new Panel("http://username:password@localhost:2053"); - -const inboundId = 1; -const clientUUID = "95e4e7bb-7796-47e7-e8a7-f4055194f776"; - -await local.updateClient(inboundId, clientUUID, { - email: "updatedEmail", -}); diff --git a/examples/update-inbound.ts b/examples/update-inbound.ts deleted file mode 100644 index 6c9e4c8..0000000 --- a/examples/update-inbound.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Panel } from "3x-ui"; - -const local = new Panel("http://username:password@localhost:2053"); -const updatedInbound = await local.updateInbound(1, { - remark: "Updated inbound", -}); - -// { -// id: 1, -// up: 0, -// down: 0, -// total: 0, -// remark: "Updated inbound", -// enable: true, -// expiryTime: 0, -// clientStats: null, -// listen: "", -// port: 48965, -// protocol: "vmess", -// settings: -// '{"clients": [ { "alterId": 0, "email": "xn1aaiwm", "enable": true, "expiryTime": 0, "id": "1db9f8ba-d1ad-4b0e-cea2-6edf9947dae5", "limitIp": 0, "subId": "", "tgId": "", "totalGB": 0 }],"decryption": "none","fallbacks": []\n}', -// streamSettings: -// '{"network":"ws","security":"none","wsSettings":{"acceptProxyProtocol":false,"path":"/","headers":{}}}', -// tag: "inbound-48965", -// sniffing: '{"enabled":true,"destOverride":["http","tls"]}', -// } diff --git a/package-lock.json b/package-lock.json index e7aef62..03ab486 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "3x-ui", - "version": "1.6.2", + "version": "1.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "3x-ui", - "version": "1.6.2", + "version": "1.7.1", "dependencies": { "async-mutex": "^0.5.0", "axios": "^1.6.4", @@ -14,7 +14,9 @@ "proxy-agent": "^6.3.1", "qs": "^6.11.2", "url-join": "^5.0.0", - "winston": "^3.11.0" + "winston": "^3.11.0", + "xray-zod": "file:../xray-zod", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20.10.4", @@ -25,8 +27,31 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.0.1", "prettier": "^3.1.0", + "resolve-tspaths": "^0.8.22", "tsx": "^4.6.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vite-tsconfig-paths": "^5.0.1", + "vitest": "^2.1.2" + } + }, + "../xray-zod": { + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.11.24", + "@typescript-eslint/eslint-plugin": "^7.5.0", + "@typescript-eslint/parser": "^7.5.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "prettier": "^3.2.5", + "resolve-tspaths": "^0.8.18", + "tsx": "^4.7.2", + "typescript": "^5.4.4", + "vitest": "^1.4.0" + }, + "peerDependencies": { + "zod": "^3.22.4" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -56,6 +81,358 @@ "kuler": "^2.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "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", @@ -205,6 +582,12 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -252,11 +635,225 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -485,6 +1082,113 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vitest/expect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz", + "integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.2", + "@vitest/utils": "2.1.2", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz", + "integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==", + "dev": true, + "dependencies": { + "@vitest/spy": "^2.1.0-beta.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.2", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz", + "integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz", + "integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==", + "dev": true, + "dependencies": { + "@vitest/utils": "2.1.2", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz", + "integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.2", + "magic-string": "^0.30.11", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz", + "integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz", + "integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.2", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -533,6 +1237,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -572,6 +1285,15 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -646,6 +1368,15 @@ "node": ">=8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -665,7 +1396,23 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "engines": { - "node": ">=6" + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/chalk": { @@ -684,6 +1431,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -760,6 +1516,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -789,11 +1554,11 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -804,6 +1569,15 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -911,6 +1685,358 @@ "@esbuild/win32-x64": "0.19.11" } }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/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" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1151,6 +2277,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1341,6 +2476,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1478,6 +2627,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -1806,6 +2961,12 @@ "node": ">= 12.0.0" } }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true + }, "node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -1814,6 +2975,15 @@ "node": ">=12" } }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1871,9 +3041,27 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } }, "node_modules/natural-compare": { "version": "1.4.0", @@ -2050,6 +3238,27 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2062,6 +3271,34 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2195,6 +3432,23 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve-tspaths": { + "version": "0.8.22", + "resolved": "https://registry.npmjs.org/resolve-tspaths/-/resolve-tspaths-0.8.22.tgz", + "integrity": "sha512-x9loBJyTLdx3grlcNpH/Y2t8IkfadtbzYhzpo683C6olazn0/4Y3cfSBiqDA0f2vSmq5tITKJCN9e1ezBh6jhA==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.3", + "commander": "12.1.0", + "fast-glob": "3.3.2" + }, + "bin": { + "resolve-tspaths": "dist/main.js" + }, + "peerDependencies": { + "typescript": ">=3.0.3" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -2220,6 +3474,41 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2345,6 +3634,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -2406,6 +3701,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -2419,6 +3723,18 @@ "node": "*" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2490,6 +3806,45 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2522,6 +3877,26 @@ "typescript": ">=4.2.0" } }, + "node_modules/tsconfck": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.3.tgz", + "integrity": "sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==", + "dev": true, + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -2619,6 +3994,223 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/vite": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz", + "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.6", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz", + "integrity": "sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz", + "integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.2", + "@vitest/mocker": "2.1.2", + "@vitest/pretty-format": "^2.1.2", + "@vitest/runner": "2.1.2", + "@vitest/snapshot": "2.1.2", + "@vitest/spy": "2.1.2", + "@vitest/utils": "2.1.2", + "chai": "^5.1.1", + "debug": "^4.3.6", + "magic-string": "^0.30.11", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.2", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.2", + "@vitest/ui": "2.1.2", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2634,6 +4226,22 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/winston": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", @@ -2674,6 +4282,10 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xray-zod": { + "resolved": "../xray-zod", + "link": true + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -2691,6 +4303,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 6e2c324..c46a17e 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,9 @@ }, "scripts": { "prepublish": "npm run build", - "build": "tsc -p tsconfig.json", - "format": "prettier --write ." + "build": "tsc -p tsconfig.build.json && resolve-tspaths", + "format": "prettier --write .", + "test": "vitest ." }, "devDependencies": { "@types/node": "^20.10.4", @@ -24,8 +25,11 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.0.1", "prettier": "^3.1.0", + "resolve-tspaths": "^0.8.22", "tsx": "^4.6.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vite-tsconfig-paths": "^5.0.1", + "vitest": "^2.1.2" }, "dependencies": { "async-mutex": "^0.5.0", @@ -34,7 +38,9 @@ "proxy-agent": "^6.3.1", "qs": "^6.11.2", "url-join": "^5.0.0", - "winston": "^3.11.0" + "winston": "^3.11.0", + "xray-zod": "file:../xray-zod", + "zod": "^3.23.8" }, "keywords": [ "xray", diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..9e01c52 --- /dev/null +++ b/src/api.ts @@ -0,0 +1,666 @@ +import type { Inbound, InboundOptions } from "$lib/types/inbound"; +import type { Client, ClientOptions } from "$lib/types"; +import { createLogger } from "$lib/logger"; +import { decodeUri } from "$lib/utils/decodeUri"; +import { parseInbound } from "$lib/utils/parseInbound"; +import { Mutex } from "async-mutex"; +import { ProxyAgent } from "proxy-agent"; +import NodeCache from "node-cache"; +import Axios from "axios"; +import urlJoin from "url-join"; +import qs from "qs"; + +export class Api { + readonly host: string; + readonly port: number; + readonly protocol: string; + readonly path: string; + readonly username: string; + private readonly _password: string; + private readonly _logger; + private readonly _cache; + private readonly _axios; + private readonly _mutex; + private _cookie; + + constructor(uri: string) { + const xui = decodeUri(uri); + this.protocol = xui.protocol; + this.host = xui.host; + this.port = xui.port; + this.path = xui.path; + this.username = xui.username; + this._password = xui.password; + + this._logger = createLogger(`[API][${this.host}]`); + this._logger.silent = true; + + this._cache = new NodeCache(); + this._cache.options.stdTTL = 10; + + this._mutex = new Mutex(); + this._cookie = ""; + + this._axios = Axios.create({ + baseURL: xui.endpoint, + proxy: false, + httpAgent: new ProxyAgent(), + httpsAgent: new ProxyAgent(), + validateStatus: () => true, + }); + } + + set debug(enable: boolean) { + this._logger.silent = !enable; + } + + set stdTTL(ttl: number) { + this._cache.options.stdTTL = ttl; + this._logger.info(`Cache ttl set to ${ttl === 0 ? "infinity" : ttl}s`); + } + + flushCache() { + this._cache.flushStats(); + this._cache.flushAll(); + } + + private async login() { + if (this._cookie) { + return; + } + + const cerdentials = qs.stringify({ + username: this.username, + password: this._password, + }); + + try { + this._logger.debug("POST /login"); + const response = await this._axios.post("/login", cerdentials, { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }); + + if ( + response.status !== 200 || + !response.data.success || + !response.headers["set-cookie"] + ) { + this._cookie = ""; + this._logger.error("Failed to initialize session"); + throw new Error("Failed to initialize session"); + } + + this._cookie = response.headers["set-cookie"][0]; + this._logger.info("Session initialized"); + } catch (err) { + if (err instanceof Axios.AxiosError) { + this._logger.http(err); + this._logger.error("Failed to initialize session"); + } + + throw err; + } + } + + private async get(path: string, params?: unknown) { + const endpoint = urlJoin("/panel/api/inbounds", path); + this._logger.debug(`GET ${endpoint}`); + + try { + await this.login(); + const response = await this._axios.get(endpoint, { + data: qs.stringify(params), + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + Cookie: this._cookie, + }, + }); + + if (response.status !== 200 || !response.data.success) { + this._logger.error(`${path} have failed.`); + throw new Error(`${path} have failed.`); + } + + return response.data.obj as T; + } catch (err) { + if (err instanceof Axios.AxiosError) { + this._logger.http(err); + this._logger.error(`GET request failed: ${endpoint}`); + } + + throw err; + } + } + + private async post(path: string, params?: unknown) { + const endpoint = urlJoin("/panel/api/inbounds", path); + this._logger.debug(`POST ${endpoint}`); + + try { + await this.login(); + const data = JSON.stringify(params); + const response = await this._axios.post(endpoint, data, { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Cookie: this._cookie, + }, + }); + + if (response.status !== 200 || !response.data.success) { + this._logger.error(`${endpoint} have failed.`); + throw new Error(`${endpoint} have failed.`); + } + + return response.data.obj as T; + } catch (err) { + if (err instanceof Axios.AxiosError) { + this._logger.http(err); + this._logger.error(`POST request failed: ${endpoint}`); + } + + throw err; + } + } + + private async cacheInbound(inbound: Inbound) { + this._logger.debug(`Inbound ${inbound.id} saved in cache.`); + this._cache.set(`inbound:${inbound.id}`, inbound); + + const hasSettings = "settings" in inbound; + if (!hasSettings || "clients" in inbound.settings === false) { + this._logger.debug(`Inbound ${inbound.id} has no clients.`); + return; + } + + inbound.settings.clients?.forEach((client) => { + let clientId: string = client.email || ""; + if ("id" in client) clientId = client.id; + if ("password" in client) clientId = client.password; + if (clientId === "") return; + + this._cache.set(`client:id:${client.email}`, clientId); + this._cache.set(`client:options:${clientId}`, client); + this._cache.set(`client:options:${client.email}`, client); + }); + + if (!inbound.clientStats) return; + inbound.clientStats.forEach((client) => { + const clientId = this._cache.get(`client:id:${client.email}`); + if (clientId) this._cache.set(`client:stat:${clientId}`, client); + this._cache.set(`client:stat:${client.email}`, client); + }); + } + + async checkHealth() { + const release = await this._mutex.acquire(); + + try { + this._logger.debug("Checking health..."); + await this.get("/list"); + this._logger.debug("Health check passed."); + return true; + } catch (err) { + this._logger.warn("Health check failed."); + return false; + } finally { + release(); + } + } + + async getInbounds() { + if (this._cache.has("inbounds")) { + this._logger.debug("Inbounds loaded from cache."); + return this._cache.get("inbounds") as Inbound[]; + } + + const release = await this._mutex.acquire(); + + try { + this._logger.debug("Fetching inbounds..."); + const inbounds = await this.get("/list"); + this._cache.set("inbounds", inbounds); + this._logger.debug("Inbounds loaded from API."); + return inbounds.map((inbound) => { + const result = parseInbound(inbound); + this.cacheInbound(result); + return result; + }); + } catch (err) { + this._logger.error("Failed to fetch inbounds."); + return []; + } finally { + release(); + } + } + + async getInbound(id: number) { + if (this._cache.has(`inbound:${id}`)) { + this._logger.debug(`Inbound ${id} loaded from cache.`); + return this._cache.get(`inbound:${id}`) as Inbound; + } + + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Fetching inbound ${id}...`); + const inbound = await this.get(`/get/${id}`); + this._cache.set(`inbound:${id}`, inbound); + this._logger.debug(`Inbound ${id} loaded from API.`); + const result = parseInbound(inbound); + this.cacheInbound(result); + return result; + } catch (err) { + this._logger.error(err); + return null; + } finally { + release(); + } + } + + async addInbound(options: InboundOptions) { + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Adding inbound ${options.remark}.`); + const inbound = await this.post("/add", { + ...options, + settings: JSON.stringify(options.settings), + streamSettings: JSON.stringify(options.streamSettings), + sniffing: JSON.stringify(options.sniffing), + }); + this._logger.info(`Inbound ${inbound.remark} added.`); + this.flushCache(); + const result = parseInbound(inbound); + this.cacheInbound(result); + return result; + } catch (err) { + this._logger.error(err); + return null; + } finally { + release(); + } + } + + async updateInbound(id: number, options: Partial) { + const oldInbound = await this.getInbound(id); + if (!oldInbound) { + this._logger.warn(`Inbound ${id} not found. Skipping update.`); + return null; + } + + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Updating inbound ${id}.`); + const data = { ...oldInbound, ...options }; + const inbound = await this.post(`/update/${id}`, { + ...data, + settings: JSON.stringify(data.settings), + streamSettings: JSON.stringify(data.streamSettings), + sniffing: JSON.stringify(data.sniffing), + }); + + this._logger.info(`Inbound ${inbound.remark} updated.`); + this.flushCache(); + const result = parseInbound(inbound); + this.cacheInbound(result); + return result; + } catch (err) { + this._logger.error(err); + return null; + } finally { + release(); + } + } + + async resetInboundsStat() { + const release = await this._mutex.acquire(); + + try { + this._logger.debug("Resetting inbounds stat..."); + await this.post(`/resetAllTraffics`); + this.flushCache(); + this._logger.info("Inbounds stat reseted."); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } + + async resetInboundStat(id: number) { + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Resetting inbound ${id} stat...`); + await this.post(`/resetAllClientTraffics/${id}`); + this.flushCache(); + this._logger.info(`Inbound ${id} stat reseted.`); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } + + async deleteInbound(id: number) { + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Deleting inbound ${id}.`); + await this.post(`/del/${id}`); + this._logger.info(`Inbound ${id} deleted.`); + this.flushCache(); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } + + async getClient(clientId: string) { + if (this._cache.has(`client:stat:${clientId}`)) { + this._logger.debug(`Client ${clientId} loaded from cache.`); + return this._cache.get(`client:stat:${clientId}`) as Client; + } + + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Fetching client ${clientId}...`); + const fetchEndpoint = `/getClientTraffics/${clientId}`; + const client = await this.get(fetchEndpoint); + if (client) { + this._logger.debug(`Client ${clientId} loaded from API.`); + this._cache.set(`client:stat:${clientId}`, client); + return client; + } + } catch (err) { + this._logger.error(err); + return null; + } finally { + release(); + } + + this._logger.debug(`Fetching client ${clientId} from inbounds...`); + await this.getInbounds(); + + if (this._cache.has(`client:stat:${clientId}`)) { + this._logger.debug(`Client ${clientId} loaded from cache.`); + return this._cache.get(`client:stat:${clientId}`) as Client; + } + + return null; + } + + async getClientOptions(clientId: string) { + if (this._cache.has(`client:options:${clientId}`)) { + this._logger.debug(`Client ${clientId} options loaded from cache.`); + return this._cache.get(`client:options:${clientId}`) as ClientOptions; + } + + await this.getInbounds(); + if (this._cache.has(`client:options:${clientId}`)) { + this._logger.debug(`Client ${clientId} options loaded from cache.`); + return this._cache.get(`client:options:${clientId}`) as ClientOptions; + } + + return null; + } + + async addClient(inboundId: number, options: ClientOptions) { + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Adding client ${options.email}.`); + await this.post("/addClient", { + id: inboundId, + settings: JSON.stringify({ + clients: [options], + }), + }); + this._logger.info(`Client ${options.email} added.`); + this.flushCache(); + return this.getClient(options.email); + } catch (err) { + this._logger.error(err); + return null; + } finally { + release(); + } + } + + async updateClient(clientId: string, options: Partial) { + this._logger.debug(`Updating client ${clientId}.`); + + const oldClient = await this.getClient(clientId); + const oldClientOptions = await this.getClientOptions(clientId); + if (!oldClient || !oldClientOptions) { + this._logger.warn(`Client ${clientId} not found. Skipping update.`); + return null; + } + + const release = await this._mutex.acquire(); + + try { + let id: string = ""; + if ("id" in oldClientOptions) id = oldClientOptions.id; + if ("password" in oldClientOptions) id = oldClientOptions.password; + + await this.post(`/updateClient/${id}`, { + id: oldClient.inboundId, + settings: JSON.stringify({ + clients: [ + { + ...oldClientOptions, + ...options, + }, + ], + }), + }); + + this._logger.info(`Client ${clientId} updated.`); + this.flushCache(); + return this.getClient(clientId); + } catch (err) { + this._logger.error(err); + return null; + } finally { + release(); + } + } + + async deleteClient(clientId: string) { + this._logger.debug(`Deleting client ${clientId}.`); + + const client = await this.getClient(clientId); + const options = await this.getClientOptions(clientId); + if (!client || !options) { + this._logger.warn(`Client ${clientId} not found. Skipping.`); + return; + } + + const release = await this._mutex.acquire(); + + try { + let id = options.email; + if ("id" in options) id = options.id; + if ("password" in options) id = options.password; + await this.post(`/${client.inboundId}/delClient/${id}`); + this.flushCache(); + this._logger.info(`Client ${clientId} deleted.`); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } + + async getClientIps(clientId: string) { + this._logger.debug(`Fetching client ${clientId} ips...`); + + if (this._cache.has(`client:ips:${clientId}`)) { + this._logger.debug(`Client ${clientId} ips loaded from cache.`); + return this._cache.get(`client:ips:${clientId}`) as string[]; + } + + const client = await this.getClient(clientId); + if (!client) { + this._logger.warn(`Client ${clientId} not found. Skipping.`); + return []; + } + + const release = await this._mutex.acquire(); + + try { + const data = await this.post(`/clientIps/${client.email}`); + if (data === "No IP Record") { + this._logger.debug(`Client ${clientId} has no IPs.`); + return []; + } + + const ips = data.split(/,|\s/gm).filter((ip) => ip.length); + this._cache.set(`client:ips:${client.email}`, ips); + this._cache.set(`client:ips:${clientId}`, ips); + this._logger.debug(`Client ${clientId} ips loaded from API.`); + return ips; + } catch (err) { + this._logger.error(err); + return []; + } finally { + release(); + } + } + + async resetClientIps(clientId: string) { + this._logger.debug(`Resetting client ${clientId} ips...`); + const client = await this.getClient(clientId); + if (!client) { + this._logger.warn(`Client ${clientId} not found. Skipping.`); + return false; + } + + const release = await this._mutex.acquire(); + + try { + await this.post(`/clearClientIps/${client.email}`); + this._cache.del(`client:ips:${client.email}`); + this._cache.del(`client:ips:${clientId}`); + this._logger.debug(`Client ${clientId} ips reseted.`); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } + + async resetClientStat(clientId: string) { + this._logger.debug(`Resetting client ${clientId} stat...`); + + const client = await this.getClient(clientId); + if (!client) { + this._logger.warn(`Client ${clientId} not found. Skipping.`); + return false; + } + + const release = await this._mutex.acquire(); + + try { + const inboundId = client.inboundId; + await this.post(`/${inboundId}/resetClientTraffic/${client.email}`); + this._logger.info(`Client ${client.email} stat reseted.`); + this.flushCache(); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } + + async deleteDepletedClients() { + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Deleting depleted clients...`); + await this.post("/delDepletedClients/-1"); + this.flushCache(); + this._logger.info(`Depleted clients deleted.`); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } + + async deleteInboundDepletedClients(inboundId: number) { + const release = await this._mutex.acquire(); + + try { + this._logger.debug(`Deleting depleted clients of inbound ${inboundId}...`); + await this.post(`/delDepletedClients/${inboundId}`); + this.flushCache(); + this._logger.info(`Depleted clients of inbound ${inboundId} deleted.`); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } + + async getOnlineClients() { + if (this._cache.has("clients:online")) { + this._logger.debug("Online clients loaded from cache."); + return this._cache.get("clients:online") as string[]; + } + + const release = await this._mutex.acquire(); + + try { + const emails = await this.post("/onlines"); + this._cache.set("clients:online", emails); + this._logger.debug("Online clients loaded from API."); + return emails; + } catch (err) { + this._logger.error(err); + return []; + } finally { + release(); + } + } + + async sendBackup() { + const release = await this._mutex.acquire(); + + try { + this._logger.debug("Sending backup..."); + await this.get("/createbackup"); + this._logger.info("Backup sent."); + return true; + } catch (err) { + this._logger.error(err); + return false; + } finally { + release(); + } + } +} diff --git a/src/index.ts b/src/index.ts index 356ac0b..f5dcea9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export type * from "./types.js"; -export * from "./panel.js"; +export * from "$lib/types"; +export * from "./api"; diff --git a/src/lib/logger/colorizeFormat.ts b/src/lib/logger/colorizeFormat.ts new file mode 100644 index 0000000..03b9817 --- /dev/null +++ b/src/lib/logger/colorizeFormat.ts @@ -0,0 +1,15 @@ +import winston from "winston"; + +export const colorizeFormat = winston.format.colorize({ + message: true, + level: true, + colors: { + info: "blue", + error: "red", + warn: "yellow", + debug: "cyan", + verbose: "white", + http: "magenta", + silly: "gray", + }, +}); diff --git a/src/lib/logger/consoleTransport.ts b/src/lib/logger/consoleTransport.ts new file mode 100644 index 0000000..88b105a --- /dev/null +++ b/src/lib/logger/consoleTransport.ts @@ -0,0 +1,13 @@ +import chalk from "chalk"; +import winston from "winston"; +import { colorizeFormat } from "./colorizeFormat.js"; + +export const consoleTransport = new winston.transports.Console({ + format: winston.format.combine( + colorizeFormat, + winston.format.printf(({ level, label, message }) => { + label = chalk.bold.white(label); + return `[${label}][${level}]: ${message}`; + }), + ), +}); diff --git a/src/lib/logger/createLogger.ts b/src/lib/logger/createLogger.ts new file mode 100644 index 0000000..4246fb9 --- /dev/null +++ b/src/lib/logger/createLogger.ts @@ -0,0 +1,10 @@ +import winston from "winston"; +import { consoleTransport } from "./consoleTransport.js"; + +export const createLogger = (name: string) => { + return winston.createLogger({ + level: "silly", + format: winston.format.label({ label: name }), + transports: [consoleTransport], + }); +}; diff --git a/src/lib/logger/index.ts b/src/lib/logger/index.ts new file mode 100644 index 0000000..23ccd23 --- /dev/null +++ b/src/lib/logger/index.ts @@ -0,0 +1 @@ +export { createLogger } from "./createLogger.js"; diff --git a/src/lib/types/client.ts b/src/lib/types/client.ts new file mode 100644 index 0000000..da21c7d --- /dev/null +++ b/src/lib/types/client.ts @@ -0,0 +1,68 @@ +export type Client = { + id: number; + inboundId: number; + enable: boolean; + email: string; + up: number; + down: number; + expiryTime: number; + total: number; + reset: number; +}; + +export type ClientVmessOptions = { + id: string; + email: string; + limitIp: number; + totalGB: number; + expiryTime: number; + enable: boolean; + tgId?: number | string; + subId?: string; + reset?: number; +}; + +export type ClientVlessOptions = { + id: string; + flow?: string; + email: string; + limitIp: number; + totalGB: number; + expiryTime: number; + enable: boolean; + tgId?: number; + subId?: string; + reset?: number; +}; + +export type ClientTrojanOptions = { + password: string; + flow?: string; + email: string; + limitIp: number; + totalGB: number; + expiryTime: number; + enable: boolean; + tgId?: number; + subId?: string; + reset?: number; +}; + +export type ClientShadowsocksOptions = { + method?: string; + password: string; + email: string; + limitIp: number; + totalGB: number; + expiryTime: number; + enable: boolean; + tgId?: number; + subId?: string; + reset?: number; +}; + +export type ClientOptions = + | ClientVmessOptions + | ClientVlessOptions + | ClientTrojanOptions + | ClientShadowsocksOptions; diff --git a/src/lib/types/inbound.ts b/src/lib/types/inbound.ts new file mode 100644 index 0000000..1324511 --- /dev/null +++ b/src/lib/types/inbound.ts @@ -0,0 +1,30 @@ +import type { Client, ClientOptions } from "./client"; +import type { InboundObject } from "xray-zod"; + +export type Inbound = Pick< + InboundObject, + "protocol" | "settings" | "streamSettings" | "sniffing" +> & { + id: number; + up: number; + down: number; + total: number; + remark: string; + enable: boolean; + expiryTime: number; + clientStats: Client[]; + listen: string; + port: number; + tag: string; +}; + +export type InboundOptions = Pick< + InboundObject, + "protocol" | "settings" | "streamSettings" | "sniffing" +> & { + enable: boolean; + remark: string; + listen: string; + port: number; + expiryTime: number; +}; diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts new file mode 100644 index 0000000..dcaf930 --- /dev/null +++ b/src/lib/types/index.ts @@ -0,0 +1,2 @@ +export * from "./client"; +export * from "./inbound"; diff --git a/src/lib/utils/decodeUri.ts b/src/lib/utils/decodeUri.ts new file mode 100644 index 0000000..dd05ccb --- /dev/null +++ b/src/lib/utils/decodeUri.ts @@ -0,0 +1,23 @@ +import urljoin from "url-join"; + +export const decodeUri = (uri: string) => { + const url = new URL(encodeURI(uri)); + const protocol = url.protocol.slice(0, -1); + const host = url.hostname; + const defaultPort = protocol === "https" ? 443 : 80; + const port = url.port.length ? Number(url.port) : defaultPort; + const path = url.pathname; + const username = decodeURIComponent(url.username); + const password = decodeURIComponent(url.password); + const endpoint = urljoin(url.origin, path); + + return { + protocol, + host, + port, + path, + username, + password, + endpoint, + }; +}; diff --git a/src/lib/utils/parseInbound.ts b/src/lib/utils/parseInbound.ts new file mode 100644 index 0000000..16238bf --- /dev/null +++ b/src/lib/utils/parseInbound.ts @@ -0,0 +1,20 @@ +import { Inbound } from "$lib/types"; + +export const parseInbound = (inbound: Inbound): Inbound => { + if (typeof inbound.settings === "string") { + if (inbound.settings === "") inbound.settings = {}; + else inbound.settings = JSON.parse(inbound.settings); + } + + if (typeof inbound.streamSettings === "string") { + if (inbound.streamSettings === "") inbound.streamSettings = {} as any; + else inbound.streamSettings = JSON.parse(inbound.streamSettings); + } + + if (typeof inbound.sniffing === "string") { + if (inbound.sniffing === "") inbound.sniffing = {}; + else inbound.sniffing = JSON.parse(inbound.sniffing); + } + + return inbound; +}; diff --git a/src/logger.ts b/src/logger.ts deleted file mode 100644 index 0e73b84..0000000 --- a/src/logger.ts +++ /dev/null @@ -1,18 +0,0 @@ -import winston from "winston"; - -export const createLogger = (label: string) => { - return winston.createLogger({ - level: "silly", - transports: [ - new winston.transports.Console({ - format: winston.format.combine( - winston.format.label({ label }), - winston.format.colorize({ level: true, message: true }), - winston.format.printf(({ level, message }) => { - return `[${label}][${level}] ${message}`; - }), - ), - }), - ], - }); -}; diff --git a/src/mutex.ts b/src/mutex.ts deleted file mode 100644 index 971e50a..0000000 --- a/src/mutex.ts +++ /dev/null @@ -1,25 +0,0 @@ -export class Mutex { - private locked: boolean = false; - private queue: Array<() => void> = []; - - lock() { - return new Promise((resolve) => { - if (!this.locked) { - this.locked = true; - resolve(); - } else { - this.queue.push(resolve); - } - }); - } - - unlock() { - if (this.queue.length > 0) { - const nextResolve = this.queue.shift(); - if (nextResolve) nextResolve(); - else this.unlock(); - } else { - this.locked = false; - } - } -} diff --git a/src/panel.ts b/src/panel.ts deleted file mode 100644 index c8d1674..0000000 --- a/src/panel.ts +++ /dev/null @@ -1,582 +0,0 @@ -import type * as T from "./types.js"; -import { ProxyAgent } from "proxy-agent"; -import { createLogger } from "./logger.js"; -import { Mutex } from "async-mutex"; -import qs from "qs"; -import urljoin from "url-join"; -import axios from "axios"; -import cache from "node-cache"; - -export class Panel { - readonly host: string; - readonly port: number; - readonly protocol: string; - readonly path: string; - readonly username: string; - private readonly password: string; - private readonly logger; - private readonly cache = new cache(); - private readonly axios; - private readonly mutex = new Mutex(); - private cookie: string = ""; - - constructor(uri: string) { - const url = new URL(encodeURI(uri)); - this.protocol = url.protocol.slice(0, -1); - this.host = url.hostname; - this.port = url.port.length ? Number(url.port) : this.protocol === "https" ? 443 : 80; - this.path = url.pathname; - this.username = decodeURIComponent(url.username); - this.password = decodeURIComponent(url.password); - - this.logger = createLogger(`[${this.host}][${this.username}]`); - this.logger.silent = true; - this.cache.options.stdTTL = 10; - - this.axios = axios.create({ - baseURL: urljoin(`${this.protocol}://${this.host}:${this.port}`, this.path), - proxy: false, - httpAgent: new ProxyAgent(), - httpsAgent: new ProxyAgent(), - validateStatus: () => true, - headers: { - Accept: "application/json", - }, - }); - } - - /** - * Logger status - */ - set debug(enable: boolean) { - this.logger.silent = !enable; - } - - /** - * Cache standard time to live in seconds. - * 0 = infinity - */ - set stdTTL(ttl: number) { - this.cache.options.stdTTL = ttl; - } - - private async login() { - if (this.cookie.length) { - return; - } - - const cerdentials = qs.stringify({ - username: this.username, - password: this.password, - }); - - this.logger.debug("POST /login"); - const response = await this.axios - .post("/login", cerdentials, { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - }) - .catch(() => {}); - - if ( - !response || - response.status !== 200 || - !response.data.success || - !response.headers["set-cookie"] - ) { - this.logger.error("Failed to initialize session."); - throw new Error("Failed to initialize session."); - } - - this.cookie = response.headers["set-cookie"][0]; - this.logger.info(`Logged-in`); - } - - private async get(path: string, params?: unknown) { - await this.login(); - - const url = urljoin("/panel/api/inbounds", path); - this.logger.debug(`GET ${url}`); - - const response = await this.axios - .get(url, { - data: qs.stringify(params), - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Accept: "application/json", - Cookie: this.cookie, - }, - }) - .catch(() => {}); - - if (!response || response.status !== 200 || !response.data.success) { - this.logger.error(`${path} have failed.`); - throw new Error(`${path} have failed.`); - } - - return response.data.obj as T; - } - - private async post(path: string, params?: unknown) { - await this.login(); - - const url = urljoin("/panel/api/inbounds", path); - this.logger.debug(`POST ${url}`); - - const data = JSON.stringify(params); - const response = await this.axios - .post(url, data, { - headers: { - "Content-Type": "application/json", - Accept: "application/json", - Cookie: this.cookie, - }, - }) - .catch((err) => { - this.logger.error(err); - }); - - if (!response || response.status !== 200 || !response.data.success) { - this.logger.error(`${path} have failed.`); - throw new Error(`${path} have failed.`); - } - - return response.data.obj as T; - } - - private cacheInbound(inbound: T.Inbound) { - this.cache.set(`inbound:${inbound.id}`, inbound); - this.logger.debug(`Inbound ${inbound.id} saved in cache.`); - - if (inbound.settings) { - const settings = JSON.parse(inbound.settings) as { - clients: T.ClientOptions[]; - }; - - if (settings && settings.clients) { - settings.clients.map((options) => { - let clientId: string = ""; - if ("id" in options) clientId = options.id; - if ("password" in options) clientId = options.password; - - this.cache.set(`client:options:${options.email}`, options); - this.cache.set(`client:id:${options.email}`, clientId); - this.cache.set(`client:options:${clientId}`, options); - }); - } - } - - if (inbound.clientStats) { - inbound.clientStats.map((client) => { - const clientId = this.cache.get(`client:id:${client.email}`); - if (clientId) this.cache.set(`client:${clientId}`, client); - this.cache.set(`client:${client.email}`, client); - }); - } - } - - async flushCache() { - this.cache.flushStats(); - this.cache.flushAll(); - } - - async getInbounds() { - const release = await this.mutex.acquire(); - - // cache hit - if (this.cache.get("inbounds")) { - release(); - this.logger.debug("Inbounds loaded from cache."); - return this.cache.get("inbounds") as T.Inbound[]; - } - - // cache miss - const inbounds = await this.get("/list"); - this.cache.set("inbounds", inbounds); - inbounds.map((inbound) => this.cacheInbound(inbound)); - - release(); - this.logger.debug("Inbounds loaded from API."); - return inbounds; - } - - async getInbound(id: number) { - const release = await this.mutex.acquire(); - - // cache hit - if (this.cache.get(`inbound:${id}`)) { - release(); - this.logger.debug(`Inbound ${id} loaded from cache.`); - return this.cache.get(`inbound:${id}`) as T.Inbound; - } - - // cache miss - const inbound = await this.get(`/get/${id}`).catch(() => {}); - if (!inbound) { - release(); - this.logger.debug(`Inbound ${id} not founded.`); - return null; - } - - this.cacheInbound(inbound); - release(); - this.logger.debug(`Inbound ${id} loaded from API.`); - return inbound; - } - - async addInbound(options: T.InboundOptions) { - const release = await this.mutex.acquire(); - - try { - this.logger.debug(`Adding inbound ${options.remark}.`); - const inbound = await this.post("/add", options); - this.flushCache(); - this.logger.info(`Inbound ${inbound.remark} added.`); - return inbound; - } catch (err) { - this.logger.warn("Couldn't add inbound."); - return null; - } finally { - release(); - } - } - - async updateInbound(id: number, options: Partial) { - const release = await this.mutex.acquire(); - - try { - this.logger.debug(`Updating inbound ${id}.`); - const inbound = await this.getInbound(id); - if (!inbound) throw new Error("Inbound not found."); - options = { ...inbound, ...options }; - const updated = await this.post(`/update/${id}`, options); - this.flushCache(); - this.logger.info(`Inbound ${id} updated.`); - return updated; - } catch (err) { - this.logger.warn("Couldn't update inbound."); - return null; - } finally { - release(); - } - } - - async resetInboundsStat() { - const release = await this.mutex.acquire(); - - try { - await this.post(`/resetAllTraffics`).catch(() => {}); - this.logger.debug("Inbounds stat reseted."); - this.flushCache(); - return true; - } catch (err) { - this.logger.warn("Couldn't reset the inbounds stat."); - return false; - } finally { - release(); - } - } - - async resetInboundStat(id: number) { - const release = await this.mutex.acquire(); - - try { - await this.post(`/resetAllClientTraffics/${id}`).catch(() => {}); - this.logger.debug(`Inbound ${id} stat reseted.`); - this.flushCache(); - return true; - } catch (err) { - this.logger.warn(`Couldn't reset the inbound ${id} stat.`); - return false; - } finally { - release(); - } - } - - async deleteInbound(id: number) { - const release = await this.mutex.acquire(); - - try { - await this.post(`/del/${id}`).catch(() => {}); - this.logger.debug(`Inbound ${id} deleted.`); - this.flushCache(); - return true; - } catch (err) { - this.logger.warn(`Couldn't delete the inbound ${id}.`); - return false; - } finally { - release(); - } - } - - async getClient(email: string) { - const release = await this.mutex.acquire(); - - // cache hit - if (this.cache.get(`client:${email}`)) { - release(); - this.logger.debug(`Client ${email} loaded from cache.`); - return this.cache.get(`client:${email}`) as T.Client; - } - - // cache miss - const client = await this.get(`/getClientTraffics/${email}`); - if (client) { - this.cache.set(`client:${email}`, client); - this.logger.debug(`Client ${email} loaded from API.`); - release(); - return client; - } - - // search all inbounds - this.logger.debug(`Try to find client ${email} in inbounds.`); - release(); - await this.getInbounds(); - if (this.cache.get(`client:${email}`)) { - this.logger.debug(`Client id ${email} loaded from inbounds.`); - return this.cache.get(`client:${email}`) as T.Client; - } - - return null; - } - - async getClientOptions(email: string) { - // cache hit - if (this.cache.get(`client:options:${email}`)) { - this.logger.debug(`Client ${email} options loaded from cache.`); - return this.cache.get(`client:options:${email}`) as T.ClientOptions; - } - - // cache miss - await this.getInbounds(); - if (this.cache.get(`client:options:${email}`)) { - this.logger.debug(`Client ${email} options loaded from cache.`); - return this.cache.get(`client:options:${email}`) as T.ClientOptions; - } - - return null; - } - - async addClient(inboundId: number, options: T.ClientOptions) { - const release = await this.mutex.acquire(); - await this.post("/addClient", { - id: inboundId, - settings: JSON.stringify({ - clients: [options], - }), - }); - - this.flushCache(); - this.logger.debug(`Client ${options.email} added.`); - release(); - } - - async addClients(inboundId: number, clients: T.ClientOptions[]) { - const release = await this.mutex.acquire(); - await this.post("/addClient", { - id: inboundId, - settings: JSON.stringify({ clients }), - }); - - this.flushCache(); - this.logger.debug(`${clients.length} clients added.`); - release(); - } - - async updateClient(inboundId: number, clientId: string, options: Partial) { - await this.getInbound(inboundId); - const defaultOptions = await this.getClientOptions(clientId); - if (!defaultOptions) { - this.logger.warn(`Client ${clientId} not found to be updated.`); - return false; - } - - const release = await this.mutex.acquire(); - let id: string = ""; - if ("id" in defaultOptions) id = defaultOptions.id; - if ("password" in defaultOptions) id = defaultOptions.password; - - await this.post(`/updateClient/${id}`, { - id: inboundId, - settings: JSON.stringify({ - clients: [ - { - ...defaultOptions, - ...options, - }, - ], - }), - }); - - this.flushCache(); - this.logger.debug(`Client ${clientId} updated.`); - release(); - return true; - } - - async updateClients(inboundId: number, clients: T.ClientUpdate[]) { - await this.getInbound(inboundId); - - const defaults: Record = {}; - for (const client of clients) { - const defaultOptions = await this.getClientOptions(client.id); - if (!defaultOptions) continue; - defaults[client.id] = defaultOptions; - } - - const release = await this.mutex.acquire(); - for (const client of clients) { - const defaultOptions = defaults[client.id]; - if (!defaultOptions) continue; - - let id: string = ""; - if ("id" in defaultOptions) id = defaultOptions.id; - if ("password" in defaultOptions) id = defaultOptions.password; - - await this.post(`/updateClient/${id}`, { - id: inboundId, - settings: JSON.stringify({ - clients: [ - { - ...defaultOptions, - ...client.options, - }, - ], - }), - }); - - this.logger.debug(`Client ${client.id} updated.`); - } - - this.flushCache(); - this.logger.debug(`${clients.length} clients were updated.`); - release(); - } - - async getClientIps(email: string) { - if (this.cache.get(`client:ips:${email}`)) { - this.logger.debug(`Client ${email} IPs loaded from cache.`); - return this.cache.get(`client:ips:${email}`) as string[]; - } - - const data = await this.post(`/clientIps/${email}`).catch(() => {}); - if (!data || data === "No IP Record") { - this.logger.debug(`Client ${email} has no IPs.`); - return []; - } - - const ips = data.split(/,|\s/gm).filter((ip) => ip.length); - this.cache.set(`client:ips:${email}`, ips); - this.logger.debug(`Client ${email} IPs loaded from API.`); - return ips; - } - - async resetClientIps(email: string) { - try { - await this.post(`/clearClientIps/${email}`); - this.cache.del(`client:ips:${email}`); - this.logger.debug(`Client ${email} IPs reseted.`); - return true; - } catch (err) { - this.logger.warn(`Couldn't reset the client ${email} ips.`); - this.logger.error(err); - return false; - } - } - - async resetClientStat(inboundId: number, email: string) { - const release = await this.mutex.acquire(); - - try { - await this.post(`/${inboundId}/resetClientTraffic/${email}`); - this.flushCache(); - this.logger.debug(`Client ${email} stat reseted.`); - return true; - } catch (err) { - this.logger.warn(`Couldn't reset the client ${email} stat.`); - this.logger.error(err); - return false; - } finally { - release(); - } - } - - async deleteClient(inboundId: number, id: string) { - const options = await this.getClientOptions(id); - if (!options) return; - - const release = await this.mutex.acquire(); - - try { - let clientId = options.email; - if ("id" in options) clientId = options.id; - if ("password" in options) clientId = options.password; - - await this.post(`/${inboundId}/delClient/${clientId}`).catch(() => {}); - this.flushCache(); - this.logger.debug(`Client ${options.email} deleted.`); - return true; - } catch (err) { - this.logger.warn(`Couldn't delete the client ${id}.`); - this.logger.error(err); - return false; - } finally { - release(); - } - } - - async deleteDepletedClients() { - const release = await this.mutex.acquire(); - - try { - await this.post("/delDepletedClients"); - this.flushCache(); - this.logger.debug(`Depleted clients deleted.`); - } catch (err) { - this.logger.warn(`Couldn't delete the depleted clients.`); - this.logger.error(err); - } finally { - release(); - } - } - - async deleteInboundDepletedClients(inboundId: number) { - const release = await this.mutex.acquire(); - - try { - await this.post(`/delDepletedClients/${inboundId}`); - this.flushCache(); - this.logger.debug(`Depleted clients deleted.`); - return true; - } catch (err) { - this.logger.warn(`Couldn't delete the depleted clients of inbound ${inboundId}.`); - this.logger.error(err); - return false; - } finally { - release(); - } - } - - async getOnlineClients() { - if (this.cache.get("clients:online")) { - this.logger.debug("Online clients loaded from cache."); - return this.cache.get("clients:online") as string[]; - } - - const emails = await this.post("/onlines").catch(() => {}); - if (!emails) { - this.logger.error("Failed to load online clients."); - return []; - } - - this.cache.set("clients:online", emails); - this.logger.debug("Online clients loaded from API."); - return emails; - } - - async exportDatabase() { - await this.get("/createbackup").catch(() => {}); - this.logger.debug("Database exported."); - } -} diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index cef4718..0000000 --- a/src/types.ts +++ /dev/null @@ -1,118 +0,0 @@ -export type ServerOptions = { - host: string; - port: number; - protocol?: "http" | "https"; - path?: string; -}; - -export type Client = { - id: number; - inboundId: number; - enable: boolean; - email: string; - up: number; - down: number; - expiryTime: number; - total: number; - reset: number; -}; - -export type ClientOptionsForVmess = { - id: string; - email: string; - limitIp: number; - totalGB: number; - expiryTime: number; - enable: boolean; - tgId?: number; - subId?: string; - reset?: number; -}; - -export type ClientOptionsForVless = { - id: string; - flow?: string; - email: string; - limitIp: number; - totalGB: number; - expiryTime: number; - enable: boolean; - tgId?: number; - subId?: string; - reset?: number; -}; - -export type ClientOptionsForTrojan = { - password: string; - flow?: string; - email: string; - limitIp: number; - totalGB: number; - expiryTime: number; - enable: boolean; - tgId?: number; - subId?: string; - reset?: number; -}; - -export type ClientOptionsForShadowsocks = { - method?: string; - password: string; - email: string; - limitIp: number; - totalGB: number; - expiryTime: number; - enable: boolean; - tgId?: number; - subId?: string; - reset?: number; -}; - -export type ClientOptions = - | ClientOptionsForVmess - | ClientOptionsForVless - | ClientOptionsForTrojan - | ClientOptionsForShadowsocks; - -export type Inbound = { - id: number; - up: number; - down: number; - total: number; - remark: string; - enable: boolean; - expiryTime: number; - clientStats: Client[]; - listen: string; - port: number; - protocol: string; - settings: string; - streamSettings: string; - tag: string; - sniffing: string; -}; - -export type InboundOptions = { - enable: boolean; - remark: string; - listen: string; - port: number; - protocol: - | "vmess" - | "vless" - | "trojan" - | "shadowsocks" - | "dokodemo-door" - | "socks" - | "https" - | string; - expiryTime: number; - settings: string; - streamSettings: string; - sniffing: string; -}; - -export type ClientUpdate = { - id: string; - options: Partial; -}; diff --git a/test/client.test.ts b/test/client.test.ts new file mode 100644 index 0000000..f03b88f --- /dev/null +++ b/test/client.test.ts @@ -0,0 +1,189 @@ +import { assert, expect, describe, beforeAll, afterAll, it } from "vitest"; +import { randomUUID } from "crypto"; +import { Api, ClientOptions } from "3x-ui"; + +const local = new Api("http://admin:admin@localhost:2053"); +let inboundId = 0; + +beforeAll(async () => { + const inbounds = await local.getInbounds(); + const inbound = inbounds.find((inbound) => inbound.remark === "Client inbound"); + if (inbound) { + inboundId = inbound.id; + return; + } + + const newInbound = await local.addInbound({ + enable: true, + remark: "Client inbound", + listen: "127.0.0.1", + port: 48964, + protocol: "vmess", + expiryTime: 0, + settings: { + decryption: "none", + fallbacks: [], + clients: [ + { + id: "8841ba90-4734-4eba-bf7d-a9e1ad0c85f7", + email: "client@example.com", + enable: true, + expiryTime: 0, + limitIp: 0, + totalGB: 0, + } as ClientOptions as any, + ], + }, + streamSettings: { + network: "ws", + security: "none", + wsSettings: {}, + }, + sniffing: { + enabled: true, + destOverride: ["http", "tls"], + }, + }); + + assert(newInbound); + expect(newInbound.remark).toBe("Client inbound"); + inboundId = newInbound.id; +}); + +afterAll(async () => { + assert(inboundId); + await local.deleteInbound(inboundId); +}); + +describe("Client", () => { + const email = Math.random().toString(36).slice(2); + const id = randomUUID(); + + const email2 = Math.random().toString(36).slice(2); + const id2 = randomUUID(); + + it("Add Client", async () => { + const client1 = await local.addClient(inboundId, { + email: email, + enable: true, + expiryTime: Date.now() + Math.floor(Math.random() * 10) * 60 * 60 * 1000, + id: id, + limitIp: Math.floor(Math.random() * 10), + subId: "", + tgId: "", + totalGB: Math.floor(Math.random() * 10) * 1024 * 1024 * 1024, + }); + + assert(client1); + expect(client1.email).toBe(email); + + const client2 = await local.addClient(inboundId, { + email: email2, + enable: true, + expiryTime: Date.now() + Math.floor(Math.random() * 10) * 60 * 60 * 1000, + id: id2, + limitIp: Math.floor(Math.random() * 10), + subId: "", + tgId: "", + totalGB: Math.floor(Math.random() * 10) * 1024 * 1024 * 1024, + }); + + assert(client2); + expect(client2.email).toBe(email2); + }); + + it("Get Client By Email", async () => { + local.flushCache(); + const client = await local.getClient(email); + assert(client); + expect(client.email).toBe(email); + }); + + it("Get Client By Id", async () => { + local.flushCache(); + const client = await local.getClient(id); + assert(client); + expect(client.email).toBe(email); + }); + + it("Get Client Options By Email", async () => { + local.flushCache(); + const client = await local.getClientOptions(email); + assert(client); + expect(client.email).toBe(email); + }); + + it("Get Client Options By Id", async () => { + local.flushCache(); + const client = await local.getClientOptions(id); + assert(client); + expect(client.email).toBe(email); + }); + + it("Update Client By Email", async () => { + const client = await local.updateClient(email, { + enable: false, + }); + + assert(client); + expect(client.email).toBe(email); + + const options = await local.getClientOptions(email); + assert(options); + expect(options.enable).toBe(false); + }); + + it("Update Client By Id", async () => { + const client = await local.updateClient(id, { + enable: true, + }); + + assert(client); + expect(client.email).toBe(email); + + const options = await local.getClientOptions(id); + assert(options); + expect(options.enable).toBe(true); + }); + + it("Get Client Ips By Email", async () => { + const ips = await local.getClientIps(email); + assert(ips); + expect(ips).toBeDefined(); + }); + + it("Reset Client Ips By ID", async () => { + const result = await local.resetClientIps(id); + expect(result).toBe(true); + }); + + it("Reset Client Stat By Email", async () => { + const result = await local.resetClientStat(email); + expect(result).toBe(true); + }); + + it("Reset Client Stat By ID", async () => { + const result = await local.resetClientStat(id); + expect(result).toBe(true); + }); + + it("Delete Deprecated Clients", async () => { + const result = await local.deleteDepletedClients(); + expect(result).toBe(true); + }); + + it("Delete Deprecated Clients of Inbound", async () => { + const result = await local.deleteInboundDepletedClients(inboundId); + expect(result).toBe(true); + }); + + it("Delete Client By Email", async () => { + const result = await local.deleteClient(email); + expect(result).toBe(true); + }); + + it("Delete Client By Id", async () => { + const result = await local.deleteClient(id2); + expect(result).toBe(true); + }); +}); diff --git a/test/inbound.test.ts b/test/inbound.test.ts new file mode 100644 index 0000000..c6bae52 --- /dev/null +++ b/test/inbound.test.ts @@ -0,0 +1,95 @@ +import { assert, expect, describe, it } from "vitest"; +import { Api, ClientOptions } from "3x-ui"; + +describe("Inbound", () => { + const local = new Api("http://admin:admin@localhost:2053"); + + it("Add Inbound", async () => { + const inbound = await local.addInbound({ + enable: true, + remark: "New inbound", + listen: "127.0.0.1", + port: 48965, + protocol: "vmess", + expiryTime: 0, + settings: { + password: "password", + decryption: "none", + fallbacks: [], + clients: [ + { + id: "6641ba90-4734-4eba-bf7d-a9e1ad0c85f7", + email: "new@example.com", + enable: true, + expiryTime: 0, + limitIp: 0, + totalGB: 0, + } as ClientOptions as any, + ], + }, + streamSettings: { + network: "ws", + security: "none", + wsSettings: {}, + }, + sniffing: { + enabled: true, + destOverride: ["http", "tls"], + }, + }); + + assert(inbound); + expect(inbound.remark).toBe("New inbound"); + }); + + it("Fetch Inbounds", async () => { + const inbounds = await local.getInbounds(); + expect(inbounds).toBeDefined(); + expect(inbounds.length).toBeGreaterThan(0); + }); + + it("Fetch Inbound", async () => { + const inbounds = await local.getInbounds(); + assert(inbounds.length > 0); + local.flushCache(); + const inbound = await local.getInbound(inbounds[0].id); + assert(inbound); + expect(inbound).toBeDefined(); + expect(inbound.id).toBe(1); + }); + + it("Update Inbound", async () => { + const inbounds = await local.getInbounds(); + const inbound = inbounds.find((inbound) => inbound.remark === "New inbound"); + assert(inbound); + + const updatedInbound = await local.updateInbound(inbound.id, { + remark: "Updated inbound", + }); + + assert(updatedInbound); + expect(updatedInbound.remark).toBe("Updated inbound"); + }); + + it("Delete Inbound", async () => { + const inbounds = await local.getInbounds(); + const inbound = inbounds.find((inbound) => inbound.remark === "Updated inbound"); + assert(inbound); + const result = await local.deleteInbound(inbound.id); + expect(result).toBe(true); + const newInbounds = await local.getInbounds(); + expect(newInbounds.length).toBe(inbounds.length - 1); + }); + + it("Reset Inbounds Stat", async () => { + const result = await local.resetInboundsStat(); + expect(result).toBe(true); + }); + + it("Reset Inbound Stat", async () => { + const inbounds = await local.getInbounds(); + assert(inbounds.length > 0); + const result = await local.resetInboundStat(inbounds[0].id); + expect(result).toBe(true); + }); +}); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..86f95f3 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["test"] +} diff --git a/tsconfig.json b/tsconfig.json index d909272..20a57d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,19 @@ { - "exclude": ["node_modules", "build", "examples"], + "exclude": ["node_modules", "build"], "compilerOptions": { "strict": true, - "module": "NodeNext", + "module": "ESNext", "target": "ESNext", - "moduleResolution": "NodeNext", + "moduleResolution": "Node", "outDir": "build", "rootDirs": ["src"], "esModuleInterop": true, "declaration": true, - "lib": ["ESNext"] + "lib": ["ESNext"], + "baseUrl": ".", + "paths": { + "3x-ui": ["src/index.ts"], + "$lib/*": ["src/lib/*"] + } } } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..048a162 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import tsconfigPaths from "vite-tsconfig-paths"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + fileParallelism: false, + }, + plugins: [tsconfigPaths()], +});