From 89cc99f915aee9fbc8346ce39b67ed8345934cb2 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 11 May 2026 15:27:06 -0700 Subject: [PATCH] Initial rdp working --- package-lock.json | 71 ++---- package.json | 2 + src/app/rdp/RdpClient.tsx | 463 ++++++++++++++++++++++++++++++++++++++ src/app/rdp/page.tsx | 11 + 4 files changed, 494 insertions(+), 53 deletions(-) create mode 100644 src/app/rdp/RdpClient.tsx create mode 100644 src/app/rdp/page.tsx diff --git a/package-lock.json b/package-lock.json index 8c241554a..463bae493 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.1", "@aws-sdk/client-s3": "3.1011.0", + "@devolutions/iron-remote-desktop": "https://s3.us-east-1.amazonaws.com/static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz", + "@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.0.tgz", "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", @@ -1058,7 +1060,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1460,6 +1461,16 @@ "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", "license": "MIT" }, + "node_modules/@devolutions/iron-remote-desktop": { + "version": "0.0.0", + "resolved": "https://s3.us-east-1.amazonaws.com/static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz", + "integrity": "sha512-96z7WShjpJJhr4I2RzhXB52GcdmVFMEVvUgoQ0a20n3gATNJ+n2V3W2i8AUeMqVR38uvcyK3e+loY5T050NgQg==" + }, + "node_modules/@devolutions/iron-remote-desktop-rdp": { + "version": "0.0.0", + "resolved": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.0.tgz", + "integrity": "sha512-qkpqYOMmSU6jIdDKOWpnLL7pb0sA/Y7Yjm4wI0/VbBIRfZPH8WdKxDU5DNp8jFF60l1zbcSJeRIq5yIAvws3Hw==" + }, "node_modules/@dotenvx/dotenvx": { "version": "1.54.1", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.54.1.tgz", @@ -2354,7 +2365,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2377,7 +2387,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2400,7 +2409,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2417,7 +2425,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2434,7 +2441,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2451,7 +2457,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2468,7 +2473,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2485,7 +2489,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2502,7 +2505,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2519,7 +2521,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2536,7 +2537,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2553,7 +2553,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2576,7 +2575,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2599,7 +2597,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2622,7 +2619,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2645,7 +2641,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2668,7 +2663,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2691,7 +2685,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2714,7 +2707,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -2734,7 +2726,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -2754,7 +2745,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -2774,7 +2764,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3034,7 +3023,6 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -6981,7 +6969,6 @@ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz", "integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -8442,7 +8429,6 @@ "version": "5.90.21", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.20" }, @@ -8558,7 +8544,6 @@ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -8906,7 +8891,6 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -9002,7 +8986,6 @@ "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -9030,7 +9013,6 @@ "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -9056,7 +9038,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -9067,7 +9048,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -9154,7 +9134,8 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -9228,7 +9209,6 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -9702,7 +9682,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10152,7 +10131,6 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -10224,7 +10202,6 @@ "integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -10353,7 +10330,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -11260,7 +11236,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -11701,6 +11676,7 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, "engines": { "node": ">=20" }, @@ -12335,7 +12311,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -12421,7 +12396,6 @@ "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -12558,7 +12532,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -12952,7 +12925,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -15370,6 +15342,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -15380,6 +15353,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -15468,7 +15442,6 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.15.tgz", "integrity": "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "15.5.15", "@swc/helpers": "0.5.15", @@ -16428,7 +16401,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", @@ -16936,7 +16908,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16968,7 +16939,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -17261,7 +17231,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -18723,8 +18692,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.2", @@ -19199,7 +19167,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19627,7 +19594,6 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -19834,7 +19800,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 1da507c0d..7ed8f53fa 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "dependencies": { "@asteasolutions/zod-to-openapi": "8.4.1", "@aws-sdk/client-s3": "3.1011.0", + "@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz", + "@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.0.tgz", "@faker-js/faker": "10.3.0", "@headlessui/react": "2.2.9", "@hookform/resolvers": "5.2.2", diff --git a/src/app/rdp/RdpClient.tsx b/src/app/rdp/RdpClient.tsx new file mode 100644 index 000000000..17d12b066 --- /dev/null +++ b/src/app/rdp/RdpClient.tsx @@ -0,0 +1,463 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Checkbox } from "@/components/ui/checkbox"; +import { toast } from "@app/hooks/useToast"; +import type { + UserInteraction, + IronError +} from "@devolutions/iron-remote-desktop/dist"; + +declare module "react" { + namespace JSX { + interface IntrinsicElements { + "iron-remote-desktop": React.DetailedHTMLProps< + React.HTMLAttributes & { + scale?: string; + verbose?: string; + flexcenter?: string; + module?: unknown; + }, + HTMLElement + >; + } + } +} + +type FormState = { + username: string; + password: string; + gatewayAddress: string; + hostname: string; + domain: string; + authtoken: string; + kdcProxyUrl: string; + pcb: string; + desktopWidth: number; + desktopHeight: number; + enableClipboard: boolean; +}; + +const isIronError = (error: unknown): error is IronError => { + return ( + typeof error === "object" && + error !== null && + typeof (error as IronError).backtrace === "function" && + typeof (error as IronError).kind === "function" + ); +}; + +export default function RdpClient() { + const [form, setForm] = useState({ + username: "Administrator", + password: "Wdvwy1W*ITK-(OK.sW?nVK%?mTl30wL0", + gatewayAddress: "ws://localhost:7171/jet/rdp", + hostname: "172.31.3.58:3389", + domain: "", + authtoken: "", + kdcProxyUrl: "", + pcb: "", + desktopWidth: 1280, + desktopHeight: 720, + enableClipboard: true + }); + + const [showLogin, setShowLogin] = useState(true); + const [moduleReady, setModuleReady] = useState(false); + const [unicodeMode, setUnicodeMode] = useState(false); + const [cursorOverrideActive, setCursorOverrideActive] = useState(false); + + const userInteractionRef = useRef(null); + const backendRef = useRef(null); + const extensionsRef = useRef<{ + displayControl: (enable: boolean) => unknown; + preConnectionBlob: (pcb: string) => unknown; + kdcProxyUrl: (url: string) => unknown; + } | null>(null); + + // Load the iron-remote-desktop modules client-side and register the + // `` custom element. + useEffect(() => { + let cancelled = false; + (async () => { + const [coreMod, rdpMod] = await Promise.all([ + import("@devolutions/iron-remote-desktop/dist"), + import("@devolutions/iron-remote-desktop-rdp/dist") + ]); + if (cancelled) return; + + await rdpMod.init("INFO"); + + backendRef.current = rdpMod.Backend; + extensionsRef.current = { + displayControl: rdpMod.displayControl, + preConnectionBlob: rdpMod.preConnectionBlob, + kdcProxyUrl: rdpMod.kdcProxyUrl + }; + // Importing the package registers the custom element as a side + // effect. Touch the default export to avoid tree-shaking. + void coreMod; + + setModuleReady(true); + })().catch((err) => { + console.error("Failed to load iron-remote-desktop modules", err); + toast({ + variant: "destructive", + title: "Failed to load RDP module", + description: `${err}` + }); + }); + + return () => { + cancelled = true; + }; + }, []); + + // Attach the "ready" listener synchronously the moment the custom + // element mounts. The custom element dispatches `ready` from its own + // `onMount`, so a deferred useEffect can race and miss it. + const remoteElementRef = (el: HTMLElement | null) => { + if (!el) return; + const onReady = (e: Event) => { + const event = e as CustomEvent; + userInteractionRef.current = event.detail.irgUserInteraction; + }; + el.addEventListener("ready", onReady); + }; + + const update = (key: K, value: FormState[K]) => { + setForm((prev) => ({ ...prev, [key]: value })); + }; + + const startSession = async () => { + const userInteraction = userInteractionRef.current; + const exts = extensionsRef.current; + if (!userInteraction || !exts) { + toast({ + variant: "destructive", + title: "Not ready", + description: "RDP module is still initializing" + }); + return; + } + + if (form.authtoken === "") { + toast({ + variant: "destructive", + title: "Missing auth token", + description: + "An auth token is required to connect through the gateway" + }); + return; + } + + toast({ + title: "Connecting...", + description: "Connection in progress" + }); + + userInteraction.setEnableClipboard(form.enableClipboard); + + const builder = userInteraction + .configBuilder() + .withUsername(form.username) + .withPassword(form.password) + .withDestination(form.hostname) + .withProxyAddress(form.gatewayAddress) + .withServerDomain(form.domain) + .withAuthToken(form.authtoken) + .withDesktopSize({ + width: form.desktopWidth, + height: form.desktopHeight + }) + .withExtension(exts.displayControl(true)); + + if (form.pcb !== "") { + builder.withExtension(exts.preConnectionBlob(form.pcb)); + } + if (form.kdcProxyUrl !== "") { + builder.withExtension(exts.kdcProxyUrl(form.kdcProxyUrl)); + } + + try { + const sessionInfo = await userInteraction.connect(builder.build()); + + toast({ title: "Connected" }); + setShowLogin(false); + userInteraction.setVisibility(true); + + const termInfo = await sessionInfo.run(); + toast({ + title: "Session terminated", + description: termInfo.reason() + }); + setShowLogin(true); + } catch (err) { + setShowLogin(true); + if (isIronError(err)) { + toast({ + variant: "destructive", + title: "Connection failed", + description: err.backtrace() + }); + } else { + toast({ + variant: "destructive", + title: "Connection failed", + description: `${err}` + }); + } + } + }; + + const ui = () => userInteractionRef.current; + + const toggleCursorKind = () => { + const u = ui(); + if (!u) return; + if (cursorOverrideActive) { + u.setCursorStyleOverride(null); + } else { + u.setCursorStyleOverride('url("crosshair.png") 7 7, default'); + } + setCursorOverrideActive((v) => !v); + }; + + return ( +
+ {showLogin && ( +
+

+ RDP Test Connection +

+ +
+ + + update("hostname", e.target.value) + } + /> + + + + update("domain", e.target.value) + } + /> + + + + update("username", e.target.value) + } + /> + + + + update("password", e.target.value) + } + /> + + + + update("gatewayAddress", e.target.value) + } + /> + + + + update("authtoken", e.target.value) + } + /> + + + update("pcb", e.target.value)} + /> + +
+ + + update( + "desktopWidth", + Number(e.target.value) || 0 + ) + } + /> + + + + update( + "desktopHeight", + Number(e.target.value) || 0 + ) + } + /> + +
+ + + update("kdcProxyUrl", e.target.value) + } + /> + +
+ + update("enableClipboard", checked === true) + } + /> + +
+ + +
+
+ )} + +
+
+ + + + + + + + +
+ + {moduleReady && ( + + )} +
+
+ ); +} + +function Field({ + label, + id, + children +}: { + label: string; + id: string; + children: React.ReactNode; +}) { + return ( +
+ + {children} +
+ ); +} diff --git a/src/app/rdp/page.tsx b/src/app/rdp/page.tsx new file mode 100644 index 000000000..52893af79 --- /dev/null +++ b/src/app/rdp/page.tsx @@ -0,0 +1,11 @@ +import RdpClient from "./RdpClient"; + +export const dynamic = "force-dynamic"; + +export const metadata = { + title: "RDP Test" +}; + +export default function RdpPage() { + return ; +}