From 664723d8d3b0ee00d939dc0d98e4a629f94c155b Mon Sep 17 00:00:00 2001 From: David xu Date: Tue, 23 Jun 2026 02:54:08 +0800 Subject: [PATCH 01/26] fix: dropbear support & after reconnect input double for one keydown & use pnpm --- frontend/package-lock.json | 39 - frontend/src/terminal.ts | 36 +- pnpm-lock.yaml | 1856 ++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 3 + scripts/build-html.js | 4 +- src/ssh/algorithms.ts | 70 ++ src/ssh/crypto.ts | 125 ++- src/ssh/kex-curve25519.ts | 140 +++ src/ssh/kex-ecdh.ts | 41 +- src/ssh/kex.ts | 21 +- src/ssh/keys.ts | 14 +- src/ssh/packet.ts | 39 +- src/worker/html.ts | 26 +- src/worker/ssh-session.ts | 245 +++-- 14 files changed, 2482 insertions(+), 177 deletions(-) create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 src/ssh/algorithms.ts create mode 100644 src/ssh/kex-curve25519.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 827fa2a..5a570e5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -536,9 +536,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -553,9 +550,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -570,9 +564,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -587,9 +578,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -604,9 +592,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -621,9 +606,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -638,9 +620,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -655,9 +634,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -672,9 +648,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -689,9 +662,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -706,9 +676,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -723,9 +690,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -740,9 +704,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/frontend/src/terminal.ts b/frontend/src/terminal.ts index bf23196..b5897fb 100644 --- a/frontend/src/terminal.ts +++ b/frontend/src/terminal.ts @@ -112,6 +112,7 @@ export class SSHTerminal { } async connect(config: SSHConnectionConfig): Promise { + this.resetActiveConnection(); this.lastConfig = config; this.terminal.clear(); @@ -159,6 +160,7 @@ export class SSHTerminal { * 服务器已通过 token 获取凭据,无需前端发送 */ connectWithWebSocket(ws: WebSocket): void { + this.resetActiveConnection(); this.lastConfig = null; this.ws = ws; this.terminal.clear(); @@ -277,10 +279,31 @@ export class SSHTerminal { } } - private scheduleReconnect(): void { + private disposeConnectionDisposables(): void { + this.disposables.forEach(d => d.dispose()); + this.disposables = []; + } + + private clearReconnectTimeout(): void { if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; } + } + + private resetActiveConnection(): void { + this.stopHeartbeat(); + this.clearReconnectTimeout(); + this.disposeConnectionDisposables(); + + if (this.ws && this.ws.readyState !== WebSocket.CLOSED && this.ws.readyState !== WebSocket.CLOSING) { + this.ws.close(1000); + } + this.ws = null; + } + + private scheduleReconnect(): void { + this.clearReconnectTimeout(); this.reconnectAttempts++; const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); @@ -288,6 +311,7 @@ export class SSHTerminal { this.terminal.writeln(`\x1b[33m[*] Reconnecting in ${delay / 1000}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})...\x1b[0m`); this.reconnectTimeout = setTimeout(async () => { + this.reconnectTimeout = null; if (this.lastConfig) { this.terminal.writeln('\x1b[32m[+] Reconnecting...\x1b[0m'); try { @@ -300,16 +324,8 @@ export class SSHTerminal { } disconnect(): void { - this.stopHeartbeat(); - if (this.reconnectTimeout) { - clearTimeout(this.reconnectTimeout); - this.reconnectTimeout = null; - } this.reconnectAttempts = this.maxReconnectAttempts; - this.ws?.close(1000); - this.ws = null; - this.disposables.forEach(d => d.dispose()); - this.disposables = []; + this.resetActiveConnection(); this.lastConfig = null; this.terminal.clear(); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..51c6ea6 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1856 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20260621.1 + typescript: + specifier: ^5.7.0 + version: 5.7.2 + vitest: + specifier: ^3.0.0 + version: 3.2.6 + wrangler: + specifier: 3.80.0 + version: 3.80.0(@cloudflare/workers-types@4.20260621.1) + +packages: + + '@cloudflare/kv-asset-handler@0.3.4': + resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} + engines: {node: '>=16.13'} + + '@cloudflare/workerd-darwin-64@1.20240925.0': + resolution: {integrity: sha512-KdLnSXuzB65CbqZPm+qYzk+zkQ1tUNPaaRGYVd/jPYAxwwtfTUQdQ+ahDPwVVs2tmQELKy7ZjQjf2apqSWUfjw==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20240925.0': + resolution: {integrity: sha512-MiQ6uUmCXjsXgWNV+Ock2tp2/tYqNJGzjuaH6jFioeRF+//mz7Tv7J7EczOL4zq+TH8QFOh0/PUsLyazIWVGng==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20240925.0': + resolution: {integrity: sha512-Rjix8jsJMfsInmq3Hm3fmiRQ+rwzuWRPV1pg/OWhMSfNP7Qp2RCU+RGkhgeR9Z5eNAje0Sn2BMrFq4RvF9/yRA==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20240925.0': + resolution: {integrity: sha512-VYIPeMHQRtbwQoIjUwS/zULlywPxyDvo46XkTpIW5MScEChfqHvAYviQ7TzYGx6Q+gmZmN+DUB2KOMx+MEpCxA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20240925.0': + resolution: {integrity: sha512-C8peGvaU5R51bIySi1VbyfRgwNSSRknqoFSnSbSBI3uTN3THTB3UnmRKy7GXJDmyjgXuT9Pcs1IgaWNubLtNtw==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-shared@0.5.4': + resolution: {integrity: sha512-PNL/0TjKRdUHa1kwgVdqUNJVZ9ez4kacsi8omz+gv859EvJmsVuGiMAClY2YfJnC9LVKhKCcjqmFgKNXG9/IXA==} + engines: {node: '>=16.7.0'} + + '@cloudflare/workers-types@4.20260621.1': + resolution: {integrity: sha512-c4xrf4shZdDOK1ihh1UKzlS/3MDYiGThT/Oqr4Y3qR9NLCSNzHB7rt+Vk/LOp0ZSNjA+7WNJEQsOhpiQtpT2GA==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@esbuild-plugins/node-globals-polyfill@0.2.3': + resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} + peerDependencies: + esbuild: '*' + + '@esbuild-plugins/node-modules-polyfill@0.2.2': + resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==} + peerDependencies: + esbuild: '*' + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.17.19': + resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.17.19': + resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.17.19': + resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.17.19': + resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.17.19': + resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.17.19': + resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.17.19': + resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.17.19': + resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.17.19': + resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.17.19': + resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.17.19': + resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.17.19': + resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.17.19': + resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.17.19': + resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.17.19': + resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.17.19': + resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.17.19': + resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.17.19': + resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.17.19': + resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.17.19': + resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.17.19': + resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.17.19': + resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.10': + resolution: {integrity: sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@8.0.7': + resolution: {integrity: sha512-fuCPLPe4yY0nv6Z1rTLFCEC452jl0k7i3gF/c8hdEKpYtEpt6Sk67hTGbxx8C0wmifFGPvKYd/O8CvS6dpgxMQ==} + + '@vitest/expect@3.2.6': + resolution: {integrity: sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==} + + '@vitest/mocker@3.2.6': + resolution: {integrity: sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.6': + resolution: {integrity: sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==} + + '@vitest/runner@3.2.6': + resolution: {integrity: sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==} + + '@vitest/snapshot@3.2.6': + resolution: {integrity: sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==} + + '@vitest/spy@3.2.6': + resolution: {integrity: sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==} + + '@vitest/utils@3.2.6': + resolution: {integrity: sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==} + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + as-table@1.0.55: + resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + capnp-ts@0.7.0: + resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + + data-uri-to-buffer@2.0.0: + resolution: {integrity: sha512-YbKCNLPPP4inc0E5If4OaalBc7gpaM2MRv77Pv2VThVComLKfbGYtJcdDCViDyp1Wd4SebhHLz94vp91zbK6bw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.17.19: + resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + estree-walker@0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + exit-hook@2.2.1: + resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} + engines: {node: '>=6'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-source@2.0.12: + resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + miniflare@3.20240925.0: + resolution: {integrity: sha512-2LmQbKHf0n6ertUKhT+Iltixi53giqDH7P71+wCir3OnGyXIODqYwOECx1mSDNhYThpxM2dav8UdPn6SQiMoXw==} + engines: {node: '>=16.13'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + + nanoid@3.3.14: + resolution: {integrity: sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + engines: {node: '>= 6.13.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + ohash@1.1.6: + resolution: {integrity: sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + printable-characters@1.0.42: + resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + rollup-plugin-inject@3.0.2: + resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. + + rollup-plugin-node-polyfills@0.2.1: + resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} + + rollup-pluginutils@2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + selfsigned@2.0.1: + resolution: {integrity: sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==} + engines: {node: '>=10'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + stacktracey@2.2.0: + resolution: {integrity: sha512-ETyQEz+CzXiLjEbyJqpbp+/T79RQD/6wqFucRBIlVNZfYq2Ay7wbretD4cxpbymZlaPWx58aIhPEY1Cr8DlVvg==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + stoppable@1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} + + unenv-nightly@2.0.0-20240919-125358-9a64854: + resolution: {integrity: sha512-XjsgUTrTHR7iw+k/SRTNjh6EQgwpC9voygnoCJo5kh4hKqsSDHUW84MhL9EsHTNfLctvVBHaSw8e2k3R2fKXsQ==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.5: + resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.6: + resolution: {integrity: sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.6 + '@vitest/ui': 3.2.6 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + workerd@1.20240925.0: + resolution: {integrity: sha512-/Jj6+yLwfieZGEt3Kx4+5MoufuC3g/8iFaIh4MPBNGJOGYmdSKXvgCqz09m2+tVCYnysRfbq2zcbVxJRBfOCqQ==} + engines: {node: '>=16'} + hasBin: true + + wrangler@3.80.0: + resolution: {integrity: sha512-c9aN7Buf7XgTPpQkw1d0VjNRI4qg3m35PTg70Tg4mOeHwHGjsd74PAsP1G8BjpdqDjfWtsua7tj1g4M3/5dYNQ==} + engines: {node: '>=16.17.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20240925.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + youch@3.2.2: + resolution: {integrity: sha512-+xhTK8sY9qV3nLbWVaOUFZPdlQ3wrMKiu3dKqKkAkLaYzzkYmpWY+v+eQIAfbPu7TZhS1G5FhEV++sl8fhuT4w==} + + zod@3.22.3: + resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} + +snapshots: + + '@cloudflare/kv-asset-handler@0.3.4': + dependencies: + mime: 3.0.0 + + '@cloudflare/workerd-darwin-64@1.20240925.0': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20240925.0': + optional: true + + '@cloudflare/workerd-linux-64@1.20240925.0': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20240925.0': + optional: true + + '@cloudflare/workerd-windows-64@1.20240925.0': + optional: true + + '@cloudflare/workers-shared@0.5.4': + dependencies: + mime: 3.0.0 + zod: 3.22.3 + + '@cloudflare/workers-types@4.20260621.1': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': + dependencies: + esbuild: 0.17.19 + + '@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19)': + dependencies: + esbuild: 0.17.19 + escape-string-regexp: 4.0.0 + rollup-plugin-node-polyfills: 0.2.1 + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.17.19': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.17.19': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.17.19': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.17.19': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.17.19': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.17.19': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.17.19': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.17.19': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.17.19': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.17.19': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.17.19': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.17.19': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.17.19': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.17.19': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.17.19': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.17.19': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.17.19': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.17.19': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.17.19': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.17.19': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.17.19': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.17.19': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@fastify/busboy@2.1.1': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.4.10': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.10 + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@8.0.7': {} + + '@vitest/expect@3.2.6': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.6(vite@7.3.5)': + dependencies: + '@vitest/spy': 3.2.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.5 + + '@vitest/pretty-format@3.2.6': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.6': + dependencies: + '@vitest/utils': 3.2.6 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.6': + dependencies: + '@vitest/pretty-format': 3.2.6 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.6': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.6': + dependencies: + '@vitest/pretty-format': 3.2.6 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + as-table@1.0.55: + dependencies: + printable-characters: 1.0.42 + + assertion-error@2.0.1: {} + + binary-extensions@2.3.0: {} + + blake3-wasm@2.1.5: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + cac@6.7.14: {} + + capnp-ts@0.7.0: + dependencies: + debug: 4.4.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + cookie@0.5.0: {} + + data-uri-to-buffer@2.0.0: + dependencies: + '@types/node': 8.0.7 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + defu@6.1.7: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.17.19: + optionalDependencies: + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escape-string-regexp@4.0.0: {} + + estree-walker@0.6.1: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + exit-hook@2.2.1: {} + + expect-type@1.3.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-source@2.0.12: + dependencies: + data-uri-to-buffer: 2.0.0 + source-map: 0.6.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + js-tokens@9.0.1: {} + + loupe@3.2.1: {} + + magic-string@0.25.9: + dependencies: + sourcemap-codec: 1.4.8 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mime@3.0.0: {} + + miniflare@3.20240925.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + acorn: 8.17.0 + acorn-walk: 8.3.5 + capnp-ts: 0.7.0 + exit-hook: 2.2.1 + glob-to-regexp: 0.4.1 + stoppable: 1.1.0 + undici: 5.28.4 + workerd: 1.20240925.0 + ws: 8.21.0 + youch: 3.2.2 + zod: 3.22.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + ms@2.1.3: {} + + mustache@4.2.0: {} + + nanoid@3.3.14: {} + + node-forge@1.4.0: {} + + normalize-path@3.0.0: {} + + ohash@1.1.6: {} + + path-parse@1.0.7: {} + + path-to-regexp@6.3.0: {} + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.14 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + printable-characters@1.0.42: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + resolve.exports@2.0.3: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup-plugin-inject@3.0.2: + dependencies: + estree-walker: 0.6.1 + magic-string: 0.25.9 + rollup-pluginutils: 2.8.2 + + rollup-plugin-node-polyfills@0.2.1: + dependencies: + rollup-plugin-inject: 3.0.2 + + rollup-pluginutils@2.8.2: + dependencies: + estree-walker: 0.6.1 + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + selfsigned@2.0.1: + dependencies: + node-forge: 1.4.0 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + sourcemap-codec@1.4.8: {} + + stackback@0.0.2: {} + + stacktracey@2.2.0: + dependencies: + as-table: 1.0.55 + get-source: 2.0.12 + + std-env@3.9.0: {} + + stoppable@1.1.0: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + supports-preserve-symlinks-flag@1.0.0: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tslib@2.8.1: {} + + typescript@5.7.2: {} + + ufo@1.6.4: {} + + undici@5.28.4: + dependencies: + '@fastify/busboy': 2.1.1 + + unenv-nightly@2.0.0-20240919-125358-9a64854: + dependencies: + defu: 6.1.7 + ohash: 1.1.6 + pathe: 1.1.2 + ufo: 1.6.4 + + vite-node@3.2.4: + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.5 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.5: + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + + vitest@3.2.6: + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.6 + '@vitest/mocker': 3.2.6(vite@7.3.5) + '@vitest/pretty-format': 3.2.6 + '@vitest/runner': 3.2.6 + '@vitest/snapshot': 3.2.6 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 + chai: 5.2.0 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.5 + vite-node: 3.2.4 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + workerd@1.20240925.0: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20240925.0 + '@cloudflare/workerd-darwin-arm64': 1.20240925.0 + '@cloudflare/workerd-linux-64': 1.20240925.0 + '@cloudflare/workerd-linux-arm64': 1.20240925.0 + '@cloudflare/workerd-windows-64': 1.20240925.0 + + wrangler@3.80.0(@cloudflare/workers-types@4.20260621.1): + dependencies: + '@cloudflare/kv-asset-handler': 0.3.4 + '@cloudflare/workers-shared': 0.5.4 + '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) + '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) + blake3-wasm: 2.1.5 + chokidar: 3.6.0 + esbuild: 0.17.19 + miniflare: 3.20240925.0 + nanoid: 3.3.14 + path-to-regexp: 6.3.0 + resolve: 1.22.12 + resolve.exports: 2.0.3 + selfsigned: 2.0.1 + source-map: 0.6.1 + unenv: unenv-nightly@2.0.0-20240919-125358-9a64854 + workerd: 1.20240925.0 + xxhash-wasm: 1.1.0 + optionalDependencies: + '@cloudflare/workers-types': 4.20260621.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + ws@8.21.0: {} + + xxhash-wasm@1.1.0: {} + + youch@3.2.2: + dependencies: + cookie: 0.5.0 + mustache: 4.2.0 + stacktracey: 2.2.0 + + zod@3.22.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..b3d628c --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: set this to true or false + workerd: set this to true or false diff --git a/scripts/build-html.js b/scripts/build-html.js index e2ca750..81245ba 100644 --- a/scripts/build-html.js +++ b/scripts/build-html.js @@ -11,8 +11,8 @@ const htmlTsFile = path.join(workerDir, 'html.ts'); console.log('Building frontend...'); try { // 1. Install and build frontend - execSync('npm install', { cwd: frontendDir, stdio: 'inherit' }); - execSync('npm run build', { cwd: frontendDir, stdio: 'inherit' }); + execSync('pnpm install', { cwd: frontendDir, stdio: 'inherit' }); + execSync('pnpm run build', { cwd: frontendDir, stdio: 'inherit' }); console.log('Inlining assets...'); // 2. Read dist/index.html diff --git a/src/ssh/algorithms.ts b/src/ssh/algorithms.ts new file mode 100644 index 0000000..0c0c2f3 --- /dev/null +++ b/src/ssh/algorithms.ts @@ -0,0 +1,70 @@ +export const KEX_ALGORITHM_CURVE25519_SHA256 = 'curve25519-sha256'; +export const KEX_ALGORITHM_ECDH_NISTP256 = 'ecdh-sha2-nistp256'; + +export const SUPPORTED_KEX_ALGORITHMS = [ + KEX_ALGORITHM_CURVE25519_SHA256, + KEX_ALGORITHM_ECDH_NISTP256, +]; + +export const SUPPORTED_ENCRYPTION_ALGORITHMS = [ + 'aes256-gcm@openssh.com', + 'aes128-gcm@openssh.com', + 'aes256-ctr', + 'aes192-ctr', + 'aes128-ctr', +]; + +export const SUPPORTED_MAC_ALGORITHMS = [ + 'hmac-sha2-256', + 'hmac-sha2-512', + 'hmac-sha1', +]; + +export interface CipherSpec { + mode: 'gcm' | 'ctr'; + blockSize: number; + ivLength: number; + keyLength: number; + aead: boolean; +} + +export interface MacSpec { + length: number; + keyLength: number; +} + +const CIPHER_SPECS: Record = { + 'aes256-gcm@openssh.com': { mode: 'gcm', blockSize: 16, ivLength: 12, keyLength: 32, aead: true }, + 'aes128-gcm@openssh.com': { mode: 'gcm', blockSize: 16, ivLength: 12, keyLength: 16, aead: true }, + 'aes256-ctr': { mode: 'ctr', blockSize: 16, ivLength: 16, keyLength: 32, aead: false }, + 'aes192-ctr': { mode: 'ctr', blockSize: 16, ivLength: 16, keyLength: 24, aead: false }, + 'aes128-ctr': { mode: 'ctr', blockSize: 16, ivLength: 16, keyLength: 16, aead: false }, +}; + +const MAC_SPECS: Record = { + none: { length: 0, keyLength: 32 }, + 'hmac-sha1': { length: 20, keyLength: 20 }, + 'hmac-sha2-256': { length: 32, keyLength: 32 }, + 'hmac-sha2-512': { length: 64, keyLength: 64 }, +}; + +export function isCurve25519KEXAlgorithm(algorithm: string): boolean { + return algorithm === KEX_ALGORITHM_CURVE25519_SHA256; +} + +export function getCipherSpec(algorithm: string): CipherSpec { + const spec = CIPHER_SPECS[algorithm]; + if (!spec) throw new Error(`Unsupported cipher: ${algorithm}`); + return spec; +} + +export function getMacSpec(algorithm: string): MacSpec { + const spec = MAC_SPECS[algorithm]; + if (!spec) throw new Error(`Unsupported MAC algorithm: ${algorithm}`); + return spec; +} + +export function getMacAlgorithmsForCipher(cipher: string): string[] { + if (getCipherSpec(cipher).aead) return ['none']; + return SUPPORTED_MAC_ALGORITHMS; +} diff --git a/src/ssh/crypto.ts b/src/ssh/crypto.ts index 7a6dace..4e4ba20 100644 --- a/src/ssh/crypto.ts +++ b/src/ssh/crypto.ts @@ -1,3 +1,5 @@ +import { concat, encodeUint32 } from './utils'; + export class SSHAESGCMCipher { private key: CryptoKey | null = null; private iv: Uint8Array; @@ -32,7 +34,7 @@ export class SSHAESGCMCipher { } } - async encrypt(plaintext: Uint8Array, _seqNum?: number, aad?: Uint8Array): Promise { + async encrypt(plaintext: Uint8Array, _seqNum?: number, aad?: Uint8Array, _commit: boolean = true): Promise { if (!this.key) throw new Error('Cipher not initialized'); const nonce = new Uint8Array(this.iv); @@ -40,14 +42,14 @@ export class SSHAESGCMCipher { if (aad) alg.additionalData = aad; const encrypted = new Uint8Array( - await crypto.subtle.encrypt(alg as AesGcmParams, this.key, plaintext) + await crypto.subtle.encrypt(alg as unknown as SubtleCryptoEncryptAlgorithm, this.key, plaintext) ); this.incIV(); return encrypted; } - async decrypt(ciphertext: Uint8Array, _seqNum?: number, aad?: Uint8Array): Promise { + async decrypt(ciphertext: Uint8Array, _seqNum?: number, aad?: Uint8Array, _commit: boolean = true): Promise { if (!this.key) throw new Error('Cipher not initialized'); const nonce = new Uint8Array(this.iv); @@ -56,7 +58,7 @@ export class SSHAESGCMCipher { try { const decrypted = new Uint8Array( - await crypto.subtle.decrypt(alg as AesGcmParams, this.key, ciphertext) + await crypto.subtle.decrypt(alg as unknown as SubtleCryptoEncryptAlgorithm, this.key, ciphertext) ); this.incIV(); return decrypted; @@ -67,6 +69,121 @@ export class SSHAESGCMCipher { } } +export class SSHAESCTRCipher { + private key: CryptoKey | null = null; + private counter: Uint8Array; + private rawKey: Uint8Array; + + constructor(rawKey: Uint8Array, iv: Uint8Array) { + if (iv.length !== 16) { + throw new Error(`AES-CTR requires a 16-byte IV, got ${iv.length}`); + } + this.counter = new Uint8Array(iv); + this.rawKey = rawKey; + } + + async init(): Promise { + this.key = await crypto.subtle.importKey( + 'raw', + this.rawKey, + { name: 'AES-CTR', length: this.rawKey.length * 8 }, + false, + ['encrypt', 'decrypt'] + ); + } + + private incCounter(blocks: number): void { + for (let block = 0; block < blocks; block++) { + for (let i = 15; i >= 0; i--) { + this.counter[i]++; + if (this.counter[i] !== 0) break; + } + } + } + + async encrypt(plaintext: Uint8Array, _seqNum?: number, _aad?: Uint8Array, commit: boolean = true): Promise { + if (!this.key) throw new Error('Cipher not initialized'); + const counter = new Uint8Array(this.counter); + const encrypted = new Uint8Array( + await crypto.subtle.encrypt( + { name: 'AES-CTR', counter, length: 128 } as SubtleCryptoEncryptAlgorithm, + this.key, + plaintext + ) + ); + if (commit) { + this.incCounter(Math.ceil(plaintext.length / 16)); + } + return encrypted; + } + + async decrypt(ciphertext: Uint8Array, _seqNum?: number, _aad?: Uint8Array, commit: boolean = true): Promise { + if (!this.key) throw new Error('Cipher not initialized'); + const counter = new Uint8Array(this.counter); + try { + const decrypted = new Uint8Array( + await crypto.subtle.decrypt( + { name: 'AES-CTR', counter, length: 128 } as SubtleCryptoEncryptAlgorithm, + this.key, + ciphertext + ) + ); + if (commit) { + this.incCounter(Math.ceil(ciphertext.length / 16)); + } + return decrypted; + } catch (e) { + console.error('[CRYPTO] AES-CTR decrypt failed, ciphertextLen:', ciphertext.length, 'error:', e instanceof Error ? e.message : String(e)); + return null; + } + } +} + +export class SSHHMAC { + private key: CryptoKey | null = null; + private rawKey: Uint8Array; + private hash: 'SHA-1' | 'SHA-256' | 'SHA-512'; + readonly length: number; + + constructor(algorithm: string, rawKey: Uint8Array) { + this.rawKey = rawKey; + if (algorithm === 'hmac-sha1') { + this.hash = 'SHA-1'; + this.length = 20; + } else if (algorithm === 'hmac-sha2-256') { + this.hash = 'SHA-256'; + this.length = 32; + } else if (algorithm === 'hmac-sha2-512') { + this.hash = 'SHA-512'; + this.length = 64; + } else { + throw new Error(`Unsupported MAC algorithm: ${algorithm}`); + } + } + + async init(): Promise { + this.key = await crypto.subtle.importKey( + 'raw', + this.rawKey, + { name: 'HMAC', hash: this.hash }, + false, + ['sign', 'verify'] + ); + } + + async sign(packet: Uint8Array, seqNum: number): Promise { + if (!this.key) throw new Error('MAC not initialized'); + const data = concat(encodeUint32(seqNum), packet); + return new Uint8Array(await crypto.subtle.sign('HMAC', this.key, data)); + } + + async verify(packet: Uint8Array, seqNum: number, expected: Uint8Array): Promise { + if (!this.key) throw new Error('MAC not initialized'); + const data = concat(encodeUint32(seqNum), packet); + return crypto.subtle.verify('HMAC', this.key, expected, data); + } +} + export const REKEY_THRESHOLD = 1 << 30; export function shouldRekey(seqNum: number): boolean { diff --git a/src/ssh/kex-curve25519.ts b/src/ssh/kex-curve25519.ts new file mode 100644 index 0000000..8476e13 --- /dev/null +++ b/src/ssh/kex-curve25519.ts @@ -0,0 +1,140 @@ +import { SSH_MSG_KEX_ECDH_INIT } from '../types'; +import { concat, readUint32, encodeString, toSSHMPInt } from './utils'; + +type X25519SubtleCrypto = { + generateKey( + algorithm: { name: 'X25519' }, + extractable: boolean, + keyUsages: string[] + ): Promise; + exportKey(format: 'raw', key: CryptoKey): Promise; + importKey( + format: 'raw', + keyData: ArrayBuffer | ArrayBufferView, + algorithm: { name: 'X25519' }, + extractable: boolean, + keyUsages: string[] + ): Promise; + deriveBits( + algorithm: { name: 'X25519'; public: CryptoKey }, + baseKey: CryptoKey, + length: number + ): Promise; +}; + +function x25519Subtle(): X25519SubtleCrypto { + return crypto.subtle as unknown as X25519SubtleCrypto; +} + +function isAllZero(bytes: Uint8Array): boolean { + for (const byte of bytes) { + if (byte !== 0) return false; + } + return true; +} + +export type Curve25519KeyPair = CryptoKeyPair; + +export class Curve25519KeyExchange { + static async generateKeyPair(): Promise { + return x25519Subtle().generateKey( + { name: 'X25519' }, + true, + ['deriveBits'] + ); + } + + static async exportRawPublicKey(keyPair: Curve25519KeyPair): Promise { + return new Uint8Array( + await x25519Subtle().exportKey('raw', keyPair.publicKey) + ); + } + + static buildInit(clientRawPublicKey: Uint8Array): Uint8Array { + if (clientRawPublicKey.length !== 32) { + throw new Error(`Invalid Curve25519 client public key length: ${clientRawPublicKey.length}`); + } + + return concat( + new Uint8Array([SSH_MSG_KEX_ECDH_INIT]), + encodeString(clientRawPublicKey) + ); + } + + static parseReply(data: Uint8Array): { + hostKey: Uint8Array; + serverRawPublicKey: Uint8Array; + signature: Uint8Array; + } { + let offset = 1; + + const hostKeyLen = readUint32(data, offset); + offset += 4; + const hostKey = data.slice(offset, offset + hostKeyLen); + offset += hostKeyLen; + + const qSLen = readUint32(data, offset); + offset += 4; + const serverRawPublicKey = data.slice(offset, offset + qSLen); + offset += qSLen; + + const sigLen = readUint32(data, offset); + offset += 4; + const signature = data.slice(offset, offset + sigLen); + + return { hostKey, serverRawPublicKey, signature }; + } + + static async computeSharedSecret( + privateKey: CryptoKey, + serverRawPublicKey: Uint8Array + ): Promise { + if (serverRawPublicKey.length !== 32) { + throw new Error(`Invalid Curve25519 server public key length: ${serverRawPublicKey.length}`); + } + + const serverKey = await x25519Subtle().importKey( + 'raw', + serverRawPublicKey, + { name: 'X25519' }, + false, + [] + ); + + const sharedSecret = new Uint8Array( + await x25519Subtle().deriveBits( + { name: 'X25519', public: serverKey }, + privateKey, + 256 + ) + ); + if (isAllZero(sharedSecret)) { + throw new Error('Curve25519 key exchange failed: all-zero shared secret'); + } + return toSSHMPInt(sharedSecret); + } + + static async computeExchangeHash( + clientVersion: string, + serverVersion: string, + clientKEXInit: Uint8Array, + serverKEXInit: Uint8Array, + hostKey: Uint8Array, + clientRawPublicKey: Uint8Array, + serverRawPublicKey: Uint8Array, + sharedSecret: Uint8Array + ): Promise { + const v_c = encodeString(clientVersion); + const v_s = encodeString(serverVersion); + const i_c = encodeString(clientKEXInit); + const i_s = encodeString(serverKEXInit); + const k_s = encodeString(hostKey); + const e = encodeString(clientRawPublicKey); + const f = encodeString(serverRawPublicKey); + const k = sharedSecret; + + const data = concat(v_c, v_s, i_c, i_s, k_s, e, f, k); + const hashBuffer = await crypto.subtle.digest('SHA-256', data); + return new Uint8Array(hashBuffer); + } +} diff --git a/src/ssh/kex-ecdh.ts b/src/ssh/kex-ecdh.ts index 04c11c8..131682b 100644 --- a/src/ssh/kex-ecdh.ts +++ b/src/ssh/kex-ecdh.ts @@ -1,9 +1,34 @@ import { SSH_MSG_KEX_ECDH_INIT } from '../types'; import { concat, readUint32, encodeString, toSSHMPInt } from './utils'; +type ECDHSubtleCrypto = { + generateKey( + algorithm: { name: 'ECDH'; namedCurve: 'P-256' }, + extractable: boolean, + keyUsages: string[] + ): Promise; + exportKey(format: 'raw', key: CryptoKey): Promise; + importKey( + format: 'raw', + keyData: ArrayBuffer | ArrayBufferView, + algorithm: { name: 'ECDH'; namedCurve: 'P-256' }, + extractable: boolean, + keyUsages: string[] + ): Promise; + deriveBits( + algorithm: { name: 'ECDH'; public: CryptoKey }, + baseKey: CryptoKey, + length: number + ): Promise; +}; + +function ecdhSubtle(): ECDHSubtleCrypto { + return crypto.subtle as unknown as ECDHSubtleCrypto; +} + export class ECDHKeyExchange { static async generateKeyPair(): Promise { - return crypto.subtle.generateKey( + return ecdhSubtle().generateKey( { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveBits'] @@ -12,7 +37,7 @@ export class ECDHKeyExchange { static async exportRawPublicKey(keyPair: CryptoKeyPair): Promise { return new Uint8Array( - await crypto.subtle.exportKey('raw', keyPair.publicKey) + await ecdhSubtle().exportKey('raw', keyPair.publicKey) ); } @@ -51,7 +76,7 @@ export class ECDHKeyExchange { privateKey: CryptoKey, serverRawPublicKey: Uint8Array ): Promise { - const serverKey = await crypto.subtle.importKey( + const serverKey = await ecdhSubtle().importKey( 'raw', serverRawPublicKey, { name: 'ECDH', namedCurve: 'P-256' }, @@ -59,7 +84,7 @@ export class ECDHKeyExchange { [] ); - const sharedBits = await crypto.subtle.deriveBits( + const sharedBits = await ecdhSubtle().deriveBits( { name: 'ECDH', public: serverKey }, privateKey, 256 @@ -87,15 +112,7 @@ export class ECDHKeyExchange { const f = encodeString(serverRawPublicKey); const k = sharedSecret; - console.log('[KEX] Hash input lengths: V_C=' + v_c.length + ' V_S=' + v_s.length + - ' I_C=' + i_c.length + ' I_S=' + i_s.length + - ' K_S=' + k_s.length + ' e=' + e.length + ' f=' + f.length + ' K=' + k.length); - console.log('[KEX] V_C hex=' + Array.from(v_c).map(b => b.toString(16).padStart(2, '0')).join('')); - console.log('[KEX] V_S hex=' + Array.from(v_s).map(b => b.toString(16).padStart(2, '0')).join('')); - const data = concat(v_c, v_s, i_c, i_s, k_s, e, f, k); - console.log('[KEX] Hash input total length: ' + data.length); - const hashBuffer = await crypto.subtle.digest('SHA-256', data); return new Uint8Array(hashBuffer); } diff --git a/src/ssh/kex.ts b/src/ssh/kex.ts index 6fe6600..efc667b 100644 --- a/src/ssh/kex.ts +++ b/src/ssh/kex.ts @@ -1,5 +1,10 @@ import { SSH_MSG_KEXINIT, KEXInitMessage } from '../types'; -import { encodeUint32, concat } from './utils'; +import { + SUPPORTED_ENCRYPTION_ALGORITHMS, + SUPPORTED_KEX_ALGORITHMS, + SUPPORTED_MAC_ALGORITHMS +} from './algorithms'; +import { concat } from './utils'; export class KEXInitBuilder { static build(): Uint8Array { @@ -12,12 +17,12 @@ export class KEXInitBuilder { parts.push(cookie); const algorithmLists = [ - 'ecdh-sha2-nistp256', + SUPPORTED_KEX_ALGORITHMS.join(','), 'ssh-ed25519,ecdsa-sha2-nistp256,rsa-sha2-512,rsa-sha2-256,ssh-rsa', - 'aes256-gcm@openssh.com,aes128-gcm@openssh.com', - 'aes256-gcm@openssh.com,aes128-gcm@openssh.com', - 'none', - 'none', + SUPPORTED_ENCRYPTION_ALGORITHMS.join(','), + SUPPORTED_ENCRYPTION_ALGORITHMS.join(','), + SUPPORTED_MAC_ALGORITHMS.join(','), + SUPPORTED_MAC_ALGORITHMS.join(','), 'none', 'none', '', @@ -68,9 +73,9 @@ export function parseKEXInit(data: Uint8Array): KEXInitMessage { }; } -export function negotiate(clientList: string[], serverList: string[]): string { +export function negotiate(clientList: string[], serverList: string[], category: string = 'algorithm'): string { for (const algo of clientList) { if (serverList.includes(algo)) return algo; } - throw new Error(`No common algorithm: ${clientList} vs ${serverList}`); + throw new Error(`No common ${category}: client=[${clientList.join(',')}] server=[${serverList.join(',')}]`); } diff --git a/src/ssh/keys.ts b/src/ssh/keys.ts index 08ab4a4..05eb6da 100644 --- a/src/ssh/keys.ts +++ b/src/ssh/keys.ts @@ -5,14 +5,18 @@ export class KeyDerivation { static async deriveKeys( sharedSecret: Uint8Array, exchangeHash: Uint8Array, - sessionID: Uint8Array + sessionID: Uint8Array, + ivLengthC2S: number = 12, + ivLengthS2C: number = 12, + integrityKeyLengthC2S: number = 32, + integrityKeyLengthS2C: number = 32 ): Promise { - const ivC2S = await this.expandKey(sharedSecret, exchangeHash, 'A', sessionID, 12); - const ivS2C = await this.expandKey(sharedSecret, exchangeHash, 'B', sessionID, 12); + const ivC2S = await this.expandKey(sharedSecret, exchangeHash, 'A', sessionID, ivLengthC2S); + const ivS2C = await this.expandKey(sharedSecret, exchangeHash, 'B', sessionID, ivLengthS2C); const keyC2S = await this.expandKey(sharedSecret, exchangeHash, 'C', sessionID, 32); const keyS2C = await this.expandKey(sharedSecret, exchangeHash, 'D', sessionID, 32); - const intKeyC2S = await this.expandKey(sharedSecret, exchangeHash, 'E', sessionID, 32); - const intKeyS2C = await this.expandKey(sharedSecret, exchangeHash, 'F', sessionID, 32); + const intKeyC2S = await this.expandKey(sharedSecret, exchangeHash, 'E', sessionID, integrityKeyLengthC2S); + const intKeyS2C = await this.expandKey(sharedSecret, exchangeHash, 'F', sessionID, integrityKeyLengthS2C); return { ivClientToServer: ivC2S, diff --git a/src/ssh/packet.ts b/src/ssh/packet.ts index d85749a..98e73d6 100644 --- a/src/ssh/packet.ts +++ b/src/ssh/packet.ts @@ -13,8 +13,10 @@ export class SSHPacketParser { } async nextPacket(blockSize: number, decrypt: ( - data: Uint8Array, seq: number, aad?: Uint8Array - ) => Uint8Array | Promise | null, hasAuthTag: boolean = false): Promise { + data: Uint8Array, seq: number, aad?: Uint8Array, commit?: boolean + ) => Uint8Array | Promise | null, hasAuthTag: boolean = false, + macLength: number = 0, + verifyMac?: (packet: Uint8Array, mac: Uint8Array, seq: number) => boolean | Promise): Promise { if (hasAuthTag) { if (this.buffer.length < 4) return null; const packetLength = readUint32(this.buffer, 0); @@ -27,7 +29,7 @@ export class SSHPacketParser { const lengthField = raw.slice(0, 4); const dataToDecrypt = raw.slice(4); - const decrypted = await decrypt(dataToDecrypt, this.seqNum, lengthField); + const decrypted = await decrypt(dataToDecrypt, this.seqNum, lengthField, true); if (!decrypted) return null; const paddingLength = decrypted[0]; @@ -46,7 +48,7 @@ export class SSHPacketParser { if (this.buffer.length < blockSize) return null; const header = await decrypt( - this.buffer.slice(0, blockSize), this.seqNum + this.buffer.slice(0, blockSize), this.seqNum, undefined, false ); if (!header) return null; @@ -56,14 +58,22 @@ export class SSHPacketParser { const totalBlocks = Math.ceil((4 + packetLength) / blockSize); const totalSize = totalBlocks * blockSize; - if (this.buffer.length < totalSize) return null; + if (this.buffer.length < totalSize + macLength) return null; const encryptedPacket = this.buffer.slice(0, totalSize); - this.buffer = this.buffer.slice(totalSize); + const mac = this.buffer.slice(totalSize, totalSize + macLength); + this.buffer = this.buffer.slice(totalSize + macLength); - const decrypted = await decrypt(encryptedPacket, this.seqNum); + const decrypted = await decrypt(encryptedPacket, this.seqNum, undefined, true); if (!decrypted) return null; + if (verifyMac && macLength > 0) { + const macValid = await verifyMac(decrypted, mac, this.seqNum); + if (!macValid) { + throw new Error('Invalid packet MAC'); + } + } + const paddingLength = decrypted[4]; const payload = decrypted.slice(5, 5 + packetLength - 1 - paddingLength); @@ -73,6 +83,7 @@ export class SSHPacketParser { length: packetLength, paddingLength, payload, + mac, }; } @@ -95,7 +106,8 @@ export class SSHPacketBuilder { blockSize: number, encrypt: ((data: Uint8Array, seq: number, aad?: Uint8Array) => Uint8Array | Promise) | null, seqNum: number, - hasAuthTag: boolean = false + hasAuthTag: boolean = false, + mac?: (packet: Uint8Array, seq: number) => Uint8Array | Promise ): Promise { const packetLength = 1 + payload.length; // For AES-GCM (hasAuthTag), padding aligns the encrypted portion @@ -123,7 +135,6 @@ export class SSHPacketBuilder { packet.set(payload, 5); - const crypto = globalThis.crypto; const randomPadding = new Uint8Array(paddingLength); crypto.getRandomValues(randomPadding); packet.set(randomPadding, 5 + payload.length); @@ -138,7 +149,15 @@ export class SSHPacketBuilder { result.set(encryptedData, 4); return result; } - return await encrypt(packet, seqNum); + const encryptedPacket = await encrypt(packet, seqNum); + if (mac) { + const macBytes = await mac(packet, seqNum); + const result = new Uint8Array(encryptedPacket.length + macBytes.length); + result.set(encryptedPacket, 0); + result.set(macBytes, encryptedPacket.length); + return result; + } + return encryptedPacket; } return packet; diff --git a/src/worker/html.ts b/src/worker/html.ts index 7dba2ac..cf8ff78 100644 --- a/src/worker/html.ts +++ b/src/worker/html.ts @@ -4,7 +4,7 @@ export const HTML = ` - + CloudSSH @@ -41,15 +41,15 @@ export const HTML = ` } - - - - -
- -
-
-
-
-
-
CloudSSH
-
-
-
-
- CONNECTION_PARAMETERS - terminal -
-
-
-
SYSTEM READY. WAITING FOR INPUT.
- -
-
- - - - - - - - - -
- - + + +
+ +
+
+
+
+
+
CloudSSH
+
+
+
+
+ CONNECTION_PARAMETERS + terminal +
+
+
+
SYSTEM READY. WAITING FOR INPUT.
+ +
+
+ + + + + + + + + +
+ + `; diff --git a/src/worker/ssh-session.ts b/src/worker/ssh-session.ts index dc94980..3d4480f 100644 --- a/src/worker/ssh-session.ts +++ b/src/worker/ssh-session.ts @@ -977,10 +977,10 @@ export class SSHSession { } private sendDebug(message: string): void { - // console.log('[DEBUG] ' + message); - // try { - // this.ws.send(JSON.stringify({ type: 'status', message: '[DEBUG] ' + message })); - // } catch {} + console.log('[DEBUG] ' + message); + try { + this.ws.send(JSON.stringify({ type: 'status', message: '[DEBUG] ' + message })); + } catch {} } close(normal: boolean = false): void { From cc1af808be7d7a1d9f8a898e025f344141bc967e Mon Sep 17 00:00:00 2001 From: TanXin Date: Tue, 23 Jun 2026 13:28:31 +0800 Subject: [PATCH 15/26] =?UTF-8?q?ci:=20=E6=B7=BB=E5=8A=A0=20GitHub=20Actio?= =?UTF-8?q?ns=20=E8=87=AA=E5=8A=A8=E9=83=A8=E7=BD=B2=E5=88=B0=20Cloudflare?= =?UTF-8?q?=20Workers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..87ce025 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,26 @@ +name: Deploy to Cloudflare Workers + +on: + push: + branches: [test] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: | + npm install + cd frontend && npm install + + - name: Build frontend and deploy + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: npm run deploy From f14453435e5bfb0f85351d7d21e08f2237bcdc4d Mon Sep 17 00:00:00 2001 From: TanXin Date: Tue, 23 Jun 2026 13:31:28 +0800 Subject: [PATCH 16/26] =?UTF-8?q?fix:=20=E6=9E=84=E5=BB=BA=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E6=94=B9=E7=94=A8=20npm=20=E6=9B=BF=E4=BB=A3=20pnpm?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=20CI=20=E7=8E=AF=E5=A2=83=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/build-html.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build-html.js b/scripts/build-html.js index 81245ba..e2ca750 100644 --- a/scripts/build-html.js +++ b/scripts/build-html.js @@ -11,8 +11,8 @@ const htmlTsFile = path.join(workerDir, 'html.ts'); console.log('Building frontend...'); try { // 1. Install and build frontend - execSync('pnpm install', { cwd: frontendDir, stdio: 'inherit' }); - execSync('pnpm run build', { cwd: frontendDir, stdio: 'inherit' }); + execSync('npm install', { cwd: frontendDir, stdio: 'inherit' }); + execSync('npm run build', { cwd: frontendDir, stdio: 'inherit' }); console.log('Inlining assets...'); // 2. Read dist/index.html From b1432173c638af6a2a9f9d9e5d21696b62b46eea Mon Sep 17 00:00:00 2001 From: TanXin Date: Tue, 23 Jun 2026 13:32:13 +0800 Subject: [PATCH 17/26] =?UTF-8?q?fix:=20CI=20=E7=8E=AF=E5=A2=83=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20pnpm=20=E6=9E=84=E5=BB=BA=E5=89=8D=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 10 +++++++--- scripts/build-html.js | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 87ce025..1fcc18d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,13 +14,17 @@ jobs: with: node-version: '20' + - uses: pnpm/action-setup@v4 + with: + version: 'latest' + - name: Install dependencies run: | - npm install - cd frontend && npm install + pnpm install + cd frontend && pnpm install - name: Build frontend and deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: npm run deploy + run: pnpm run deploy diff --git a/scripts/build-html.js b/scripts/build-html.js index e2ca750..81245ba 100644 --- a/scripts/build-html.js +++ b/scripts/build-html.js @@ -11,8 +11,8 @@ const htmlTsFile = path.join(workerDir, 'html.ts'); console.log('Building frontend...'); try { // 1. Install and build frontend - execSync('npm install', { cwd: frontendDir, stdio: 'inherit' }); - execSync('npm run build', { cwd: frontendDir, stdio: 'inherit' }); + execSync('pnpm install', { cwd: frontendDir, stdio: 'inherit' }); + execSync('pnpm run build', { cwd: frontendDir, stdio: 'inherit' }); console.log('Inlining assets...'); // 2. Read dist/index.html From 9ef1f428d862e728551ae9605238b594ff9da27a Mon Sep 17 00:00:00 2001 From: TanXin Date: Tue, 23 Jun 2026 13:33:15 +0800 Subject: [PATCH 18/26] =?UTF-8?q?fix:=20=E5=8D=87=E7=BA=A7=20Node.js=20?= =?UTF-8?q?=E8=87=B3=20v22=20=E4=BB=A5=E5=85=BC=E5=AE=B9=E6=9C=80=E6=96=B0?= =?UTF-8?q?=20pnpm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1fcc18d..f8144f0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' - uses: pnpm/action-setup@v4 with: From e30e1e670781175708546d9628fb92fa9254d778 Mon Sep 17 00:00:00 2001 From: David xu Date: Tue, 23 Jun 2026 20:31:29 +0800 Subject: [PATCH 19/26] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E5=9B=BA=E5=AE=9APTY=E5=B0=BA=E5=AF=B8=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E8=BE=93=E5=87=BA=E8=A2=AB=E6=B8=85=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/terminal.ts | 30 ++++++++++---- src/types.ts | 7 ++++ src/worker/durable-object.ts | 78 ++++++++++++++++++++++++++++++------ src/worker/html.ts | 6 ++- src/worker/ssh-session.ts | 58 +++++++++++++++++++++------ 5 files changed, 145 insertions(+), 34 deletions(-) diff --git a/frontend/src/terminal.ts b/frontend/src/terminal.ts index 949cd4b..6f6c0bd 100644 --- a/frontend/src/terminal.ts +++ b/frontend/src/terminal.ts @@ -152,6 +152,7 @@ export class SSHTerminal { password: config.password, authMethod: config.authMethod, privateKey: config.privateKey, + ...this.getTerminalSize(), })); this.startHeartbeat(); @@ -188,9 +189,14 @@ export class SSHTerminal { ws.onopen = () => { this.terminal.writeln('\x1b[32m[+] WebSocket connected, authenticating...\x1b[0m'); + this.sendResize(); this.startHeartbeat(); }; + if (ws.readyState === WebSocket.OPEN) { + this.sendResize(); + } + this.setupWebSocketHandlers(); } @@ -284,13 +290,7 @@ export class SSHTerminal { // Terminal resize: send to server + update trzsz column count this.disposables.push( this.terminal.onResize(({ cols, rows }) => { - if (this.ws?.readyState === WebSocket.OPEN) { - this.ws.send(JSON.stringify({ - type: 'resize', - cols, - rows, - })); - } + this.sendResize({ cols, rows }); this.trzszFilter?.setTerminalColumns(cols); }) ); @@ -309,6 +309,22 @@ export class SSHTerminal { }, 30000); } + private getTerminalSize(): { cols: number; rows: number } { + return { + cols: this.terminal.cols, + rows: this.terminal.rows, + }; + } + + private sendResize(size = this.getTerminalSize()): void { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify({ + type: 'resize', + ...size, + })); + } + } + private stopHeartbeat(): void { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); diff --git a/src/types.ts b/src/types.ts index 2d4ac6f..90f2721 100644 --- a/src/types.ts +++ b/src/types.ts @@ -46,6 +46,13 @@ export interface SSHConnectionConfig { password: string; authMethod?: 'password' | 'publickey'; privateKey?: string; + cols?: number; + rows?: number; +} + +export interface TerminalSize { + cols: number; + rows: number; } export interface Env { diff --git a/src/worker/durable-object.ts b/src/worker/durable-object.ts index dcecb9b..c15725c 100644 --- a/src/worker/durable-object.ts +++ b/src/worker/durable-object.ts @@ -1,4 +1,4 @@ -import { Env, SSHConnectionConfig } from '../types'; +import { Env, SSHConnectionConfig, TerminalSize } from '../types'; import { SSHSession } from './ssh-session'; /** @@ -38,6 +38,7 @@ export class SSHSessionDO { private env: Env; private sessions: Map = new Map(); private _pendingTimeouts: Map> = new Map(); + private pendingTerminalSizes: Map = new Map(); constructor(state: DurableObjectState, env: Env) { this.state = state; @@ -105,26 +106,43 @@ export class SSHSessionDO { return; } + if (typeof message !== 'string') { + return; + } + + let msg: any; + try { + msg = JSON.parse(message); + } catch { + ws.send(JSON.stringify({ type: 'error', message: 'Invalid credentials format' })); + ws.close(1011, 'Invalid format'); + return; + } + + if (msg.type === 'resize') { + this.rememberTerminalSize(ws, msg.cols, msg.rows); + return; + } + if (msg.type === 'ping') { + ws.send(JSON.stringify({ type: 'pong' })); + return; + } + const timeout = this._pendingTimeouts.get(ws); if (timeout) { clearTimeout(timeout); this._pendingTimeouts.delete(ws); } - try { - const config = JSON.parse(message as string) as SSHConnectionConfig; + const config = msg as SSHConnectionConfig; - if (!config.host || !config.username || (!config.password && !config.privateKey)) { - ws.send(JSON.stringify({ type: 'error', message: 'Missing credentials' })); - ws.close(1011, 'Invalid credentials'); - return; - } - - await this.initSSHSession(ws, config); - } catch (e) { - ws.send(JSON.stringify({ type: 'error', message: 'Invalid credentials format' })); - ws.close(1011, 'Invalid format'); + if (!config.host || !config.username || (!config.password && !config.privateKey)) { + ws.send(JSON.stringify({ type: 'error', message: 'Missing credentials' })); + ws.close(1011, 'Invalid credentials'); + return; } + + await this.initSSHSession(ws, config); } async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean): Promise { @@ -138,6 +156,7 @@ export class SSHSessionDO { clearTimeout(timeout); this._pendingTimeouts.delete(ws); } + this.pendingTerminalSizes.delete(ws); } async webSocketError(ws: WebSocket, error: unknown): Promise { @@ -165,8 +184,14 @@ export class SSHSessionDO { await socket.opened; const strictVerify = this.env.STRICT_HOST_KEY_VERIFY !== 'false'; + const pendingSize = this.pendingTerminalSizes.get(ws); + if (pendingSize) { + config.cols = pendingSize.cols; + config.rows = pendingSize.rows; + } const session = new SSHSession(ws, socket, config, strictVerify); this.sessions.set(ws, session); + this.pendingTerminalSizes.delete(ws); await session.startHandshake(); @@ -179,4 +204,31 @@ export class SSHSessionDO { } catch {} } } + + private rememberTerminalSize(ws: WebSocket, cols: unknown, rows: unknown): void { + const size = this.normalizeTerminalSize(cols, rows); + if (size) this.pendingTerminalSizes.set(ws, size); + } + + private normalizeTerminalSize(cols: unknown, rows: unknown): TerminalSize | null { + if ( + typeof cols !== 'number' || + typeof rows !== 'number' || + !Number.isFinite(cols) || + !Number.isFinite(rows) + ) { + return null; + } + + const size = { + cols: Math.floor(cols), + rows: Math.floor(rows), + }; + + if (size.cols < 10 || size.cols > 1000 || size.rows < 5 || size.rows > 1000) { + return null; + } + + return size; + } } diff --git a/src/worker/html.ts b/src/worker/html.ts index 8a15870..442ce27 100644 --- a/src/worker/html.ts +++ b/src/worker/html.ts @@ -4,7 +4,7 @@ export const HTML = ` - + CloudSSH @@ -40,6 +40,7 @@ export const HTML = ` } } + @@ -40,17 +40,16 @@ export const HTML = ` } } - -