@aiti-ai/grdpwasm 0.0.0-stage → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,160 @@
1
- # Temporary Holding Version
1
+ # grdpwasm
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [English](README.md) | [中文说明](README.zh-CN.md)
4
+
5
+ A web-based RDP client built with Go WebAssembly and [grdp](https://github.com/nakagami/grdp).
6
+ Connect to a Windows Remote Desktop server directly from your browser — no plugins required.
7
+
8
+ > **This is the `aiti-aigrdpwasm` fork** of [nakagami/grdpwasm](https://github.com/nakagami/grdpwasm).
9
+ > It carries a patched `grdp` vendor fork and several feature/fix layers on top of upstream —
10
+ > see [Updates in this fork](#updates-in-this-fork) below.
11
+ > Published to npm as [`@aiti-ai/grdpwasm`](https://www.npmjs.com/package/@aiti-ai/grdpwasm).
12
+
13
+ > **Availability**: published to npm as [`@aiti-ai/grdpwasm`](https://www.npmjs.com/package/@aiti-ai/grdpwasm) —
14
+ > `npm install @aiti-ai/grdpwasm`. jsDelivr/unpkg serve `main.wasm` straight from the npm tarball;
15
+ > when served behind a backend (e.g. aiti-ui-env's rdp-bridge), the wasm loads same-origin.
16
+
17
+ ## Updates in this fork
18
+
19
+ ### xrdp / gnome-remote-desktop compatibility
20
+
21
+ - Vendored patched `grdp` fork (`third_party/grdp`): planar codec constant fix, capability
22
+ advertisement trimmed to what this client can honor (no SURFACE_COMMANDS, no frame
23
+ marker, no bitmap-cache REV2), NSCodec-capture server-type detection, fullscreen
24
+ refresh recovery, ECDSA CredSSP key echo, fast-path input PDU padding, clear NLA errors.
25
+ - Automatic EGFX negotiation: connects with GFX first; if the server's actual codec is
26
+ undecodable (xrdp sends progressive/planar only), it reconnects once in compat mode.
27
+ GDS/Windows (H.264) connect normally.
28
+ - Lossless WASM transport: inbound messages queue without blocking the JS thread or
29
+ dropping data (dropping caused stream desync → artifacting/disconnects); bursts are
30
+ bounded by the bridge's backpressure.
31
+
32
+ ### Frontend
33
+
34
+ - **fixedTarget mode**: the target host/port is provided by the server (injected
35
+ `__rdpTarget`) or `?fixedTarget=true`, and can no longer be tampered with from the
36
+ frontend; standalone mode keeps free-form connection fields.
37
+ - Virtual keyboard (programmer layout), shortcut bar, native system-keyboard proxy,
38
+ X11/RFB keysym helpers — shared, host-agnostic components.
39
+ - xrdp color-cursor mitigation: cursors whose AND mask makes them fully opaque render
40
+ as black blocks; they are detected (≥95% opaque ratio) and discarded.
41
+
42
+ ### Packaging & delivery (@aiti-ai/grdpwasm)
43
+
44
+ - Published as an npm package. The tarball ships `main.wasm` (that's what jsDelivr/unpkg
45
+ serve from), but `postinstall` removes the local copy after install — at runtime the
46
+ page race-downloads the wasm from CDNs via [justget](https://github.com/justdb/justget)
47
+ with same-origin fallback (override: `?wasm=<url>` or `localStorage['grdpwasm.wasmUrl']`).
48
+ - Server-side fallback for embedders: a backend helper (`ensureRdpWasm` pattern) can
49
+ pre-fetch the wasm into a cache dir so the static server always has one to serve.
50
+
51
+ ## Architecture
52
+
53
+ ```
54
+ Browser (WASM) ──WebSocket──► proxy (Go) ──TCP──► RDP Server
55
+ ```
56
+
57
+ Because browsers cannot open raw TCP sockets, a lightweight Go proxy server bridges
58
+ WebSocket connections from the browser to the RDP server's TCP port.
59
+
60
+ ## Requirements
61
+
62
+ - Go 1.24 or later
63
+ - A reachable RDP server (Windows or any RDP-compatible host)
64
+
65
+ ## Build
66
+
67
+ ```bash
68
+ git clone https://github.com/nakagami/grdpwasm.git
69
+ cd grdpwasm
70
+ make all
71
+ ```
72
+
73
+ `make all` produces:
74
+
75
+ | Output | Description |
76
+ |---|---|
77
+ | `static/main.wasm` | Go WASM binary (runs in the browser) |
78
+ | `static/wasm_exec.js` | Go runtime JS support file |
79
+ | `proxy/proxy` | WebSocket-to-TCP proxy + static file server |
80
+
81
+ ## Run
82
+
83
+ ```bash
84
+ make serve
85
+ # or equivalently:
86
+ ./proxy/proxy -listen :8080 -static static
87
+ ```
88
+
89
+ Then open **http://localhost:8080** in your browser.
90
+
91
+ ### Proxy options
92
+
93
+ | Flag | Default | Description |
94
+ |---|---|---|
95
+ | `-listen` | `:8080` | Address and port to listen on |
96
+ | `-static` | `static` | Directory to serve static files from |
97
+
98
+ ## Usage
99
+
100
+ 1. Open `http://localhost:8080` in a browser.
101
+ 2. Fill in the connection form:
102
+ - **Host** — hostname or IP address of the RDP server
103
+ - **Port** — RDP port (default `3389`)
104
+ - **Domain** — Windows domain (leave blank for local accounts)
105
+ - **User** — username
106
+ - **Password** — password
107
+ - **Width / Height** — initial desktop resolution
108
+ 3. Click **Connect**.
109
+ 4. The remote desktop appears in the canvas. Click the canvas to capture keyboard focus.
110
+ 5. Click **Disconnect** to end the session.
111
+
112
+ ## Keyboard & Mouse
113
+
114
+ All standard keyboard input is forwarded to the remote desktop via RDP scan codes.
115
+ Mouse move, button clicks, and scroll wheel are fully supported.
116
+
117
+ > **Note:** The browser tab must have focus for keyboard events to be forwarded.
118
+ > Click inside the canvas area if keys stop responding.
119
+
120
+ ## Audio
121
+
122
+ Remote audio is streamed via RDPSND and played through the browser's Web Audio API
123
+ (PCM 44100 Hz, stereo, 16-bit signed little-endian).
124
+
125
+ ## Security notes
126
+
127
+ - The proxy accepts connections from any origin. Run it only on a trusted network
128
+ or add authentication before exposing it to the internet.
129
+ - Credentials are transmitted from the browser to the proxy over WebSocket.
130
+ Use HTTPS/WSS (put the proxy behind a TLS-terminating reverse proxy such as
131
+ nginx or Caddy) when accessing it over an untrusted network.
132
+
133
+ ## Development
134
+
135
+ ```
136
+ npm install # install JS dependencies (justget, esbuild)
137
+ make wasm # rebuild only the WASM binary
138
+ make proxy # rebuild only the proxy server
139
+ make wasm_exec # refresh wasm_exec.js from the local Go toolchain
140
+ npm run build:vendor # rebuild static/vendor/justget.browser.js from the installed justget
141
+ make clean # remove all build artifacts
142
+ ```
143
+
144
+ ## npm package
145
+
146
+ Publish the browser assets as `@aiti-ai/grdpwasm`. Consumers get everything except
147
+ `main.wasm` — the page fetches it from CDN at runtime.
148
+
149
+ ```bash
150
+ npm run build:vendor # bundle justget/browser into static/vendor/
151
+ make all # build main.wasm + wasm_exec.js + proxy
152
+ npm publish # prepack syncs the version into index.html; tarball ships the wasm
153
+ ```
154
+
155
+ Consumers get `static/` (html/js/vendor) without `main.wasm` — the page fetches it
156
+ from CDN at runtime. Set `GRDPWASM_KEEP_WASM=1` before install to keep the local copy.
157
+
158
+ ## License
159
+
160
+ GPLv3 — see [grdp LICENSE](https://github.com/nakagami/grdp/blob/master/LICENSE).
@@ -0,0 +1,145 @@
1
+ # grdpwasm
2
+
3
+ [English](README.md) | 中文说明
4
+
5
+ 基于 Go WebAssembly 与 [grdp](https://github.com/nakagami/grdp) 构建的网页版 RDP 客户端。
6
+ 在浏览器里直连 Windows 远程桌面——无需任何插件。
7
+
8
+ > **本仓库是 [nakagami/grdpwasm](https://github.com/nakagami/grdpwasm) 的 `aiti-aigrdpwasm` fork**,
9
+ > 在上游之上带有一个补丁版 grdp vendor fork 及多层功能/修复,详见下方
10
+ > [本 fork 更新说明](#本-fork-更新说明)。
11
+ > 已发布为 npm 包 [`@aiti-ai/grdpwasm`](https://www.npmjs.com/package/@aiti-ai/grdpwasm)。
12
+
13
+ ## 本 fork 更新说明
14
+
15
+ ### xrdp / gnome-remote-desktop 兼容
16
+
17
+ - 内置补丁版 grdp fork(`third_party/grdp`):planar codec 常量修复;能力集广播裁剪到本客户端能真正
18
+ 支持的范围(不广播 SURFACE_COMMANDS、frame marker、bitmap-cache REV2);NSCodec 捕获检测用于
19
+ 服务器类型判别;全屏刷新恢复初始画面损伤;ECDSA CredSSP 公钥回显;fast-path 输入 PDU 8 字节对齐;
20
+ NLA 失败返回明确错误信息。
21
+ - EGFX 自动协商:先带 GFX 连接;若服务器实际编码本客户端无法解码(xrdp 只发 progressive/planar),
22
+ 自动以兼容模式重连一次。GDS/Windows(H.264)正常连接,无感知。
23
+ - 无损 WASM 传输层:收包队列既不阻塞 JS 线程也不丢消息(丢包 = 流失步 → 花屏/断连);突发期内存
24
+ 由桥的背压机制约束有界。
25
+
26
+ ### 前端
27
+
28
+ - **fixedTarget 模式**:目标 host/port 由服务端提供(注入 `__rdpTarget`)或经 `?fixedTarget=true` 传入,
29
+ 前端不可篡改;standalone 模式保留自由连接表单。
30
+ - 虚拟键盘(程序员布局)、快捷键条、原生系统键盘代理、X11/RFB keysym 工具——载体无关公共组件。
31
+ - xrdp 彩色光标缓解:AND 掩码导致整个矩形全不透明的光标会渲染成黑块,检测到(不透明占比 ≥95%)即丢弃。
32
+
33
+ ### 打包与分发(@aiti-ai/grdpwasm)
34
+
35
+ - 已发布为 npm 包。tarball 里带 `main.wasm`(jsDelivr/unpkg 分发的源头),但 postinstall 在安装后即删除
36
+ 本地副本——运行时页面经 [justget](https://github.com/justdb/justget) 从 CDN 竞速下载 wasm,同源兜底
37
+ (覆盖方式:`?wasm=<url>` 或 `localStorage['grdpwasm.wasmUrl']`)。
38
+ - 面向嵌入方的服务端兜底:后端可按 `ensureRdpWasm` 模式把 wasm 预取到缓存目录,静态服务随时有得 serve。
39
+
40
+ ## 架构
41
+
42
+ ```
43
+ 浏览器 (WASM) ──WebSocket──► proxy (Go) ──TCP──► RDP 服务器
44
+ ```
45
+
46
+ 浏览器无法直接开 TCP socket,由一个轻量 Go 代理把浏览器的 WebSocket 连接桥接到
47
+ RDP 服务器的 TCP 端口。
48
+
49
+ ## 环境要求
50
+
51
+ - Go 1.24 或更高
52
+ - 一台可达的 RDP 服务器(Windows 或任何 RDP 兼容主机)
53
+
54
+ ## 构建
55
+
56
+ ```bash
57
+ git clone https://github.com/nakagami/grdpwasm.git
58
+ cd grdpwasm
59
+ make all
60
+ ```
61
+
62
+ `make all` 产物:
63
+
64
+ | 产物 | 说明 |
65
+ |---|---|
66
+ | `static/main.wasm` | Go WASM 二进制(跑在浏览器里) |
67
+ | `static/wasm_exec.js` | Go 运行时 JS 支撑文件 |
68
+ | `proxy/proxy` | WebSocket↔TCP 代理 + 静态文件服务 |
69
+
70
+ ## 运行
71
+
72
+ ```bash
73
+ make serve
74
+ # 或者等价手动启动:
75
+ ./proxy/proxy -listen :8080 -static static
76
+ ```
77
+
78
+ 然后用浏览器打开 **http://localhost:8080**。
79
+
80
+ ### 代理参数
81
+
82
+ | 参数 | 默认值 | 说明 |
83
+ |---|---|---|
84
+ | `-listen` | `:8080` | 监听地址与端口 |
85
+ | `-static` | `static` | 静态文件目录 |
86
+
87
+ ## 使用
88
+
89
+ 1. 浏览器打开 `http://localhost:8080`。
90
+ 2. 填写连接表单:
91
+ - **Host** — RDP 服务器主机名或 IP
92
+ - **Port** — RDP 端口(默认 `3389`)
93
+ - **Domain** — Windows 域(本地账号留空)
94
+ - **User** — 用户名
95
+ - **Password** — 密码
96
+ - **Width / Height** — 初始桌面分辨率
97
+ 3. 点击 **Connect**。
98
+ 4. 远程桌面出现在画布里;点击画布捕获键盘焦点。
99
+ 5. 点击 **Disconnect** 结束会话。
100
+
101
+ ## 键盘与鼠标
102
+
103
+ 所有标准键盘输入经 RDP 扫描码转发到远程桌面。鼠标移动、按键点击、滚轮完整支持。
104
+
105
+ > **注意:** 浏览器标签页必须持有焦点,键盘事件才会转发。按键无响应时请点击画布区域。
106
+
107
+ ## 音频
108
+
109
+ 远程音频经 RDPSND 流式传输,通过浏览器 Web Audio API 播放
110
+ (PCM 44100 Hz、立体声、16 位有符号小端)。
111
+
112
+ ## 安全须知
113
+
114
+ - 代理接受任意来源的连接。请只在可信网络运行,或先加认证再暴露到公网。
115
+ - 凭据经 WebSocket 从浏览器传到代理。跨不可信网络访问时请使用 HTTPS/WSS
116
+ (把代理放在 nginx/Caddy 等 TLS 终结反代之后)。
117
+
118
+ ## 开发
119
+
120
+ ```
121
+ npm install # 安装 JS 依赖(justget、esbuild)
122
+ make wasm # 只重编 WASM 二进制
123
+ make proxy # 只重编代理服务
124
+ make wasm_exec # 用本地 Go 工具链刷新 wasm_exec.js
125
+ npm run build:vendor # 从已安装的 justget 重编 static/vendor/justget.browser.js
126
+ make clean # 清理全部构建产物
127
+ ```
128
+
129
+ ## npm 包
130
+
131
+ 把前端资产发布为 `@aiti-ai/grdpwasm`。消费者装到除 `main.wasm` 外的全部文件——
132
+ 页面运行时自行从 CDN 拉取。
133
+
134
+ ```bash
135
+ npm run build:vendor # 把 justget/browser 打包进 static/vendor/
136
+ make all # 构建 main.wasm + wasm_exec.js + proxy
137
+ npm publish # prepack 会把版本号同步进 index.html;tarball 带 wasm
138
+ ```
139
+
140
+ 安装方拿到的是不含 `main.wasm` 的 `static/`(html/js/vendor)——页面运行时从 CDN 拉取。
141
+ 如需保留本地副本,安装前设置 `GRDPWASM_KEEP_WASM=1`。
142
+
143
+ ## 许可证
144
+
145
+ GPLv3 —— 见 [grdp LICENSE](https://github.com/nakagami/grdp/blob/master/LICENSE)。
package/package.json CHANGED
@@ -1,6 +1,29 @@
1
1
  {
2
2
  "name": "@aiti-ai/grdpwasm",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.2",
4
+ "description": "Web-based RDP client (Go WASM + WebSocket proxy) — browser assets; main.wasm fetched from CDN at runtime via justget",
5
+ "license": "GPL-3.0",
6
+ "homepage": "https://github.com/aiti-ai/aiti-aigrdpwasm#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/aiti-ai/aiti-aigrdpwasm.git"
10
+ },
11
+ "files": [
12
+ "static/",
13
+ "scripts/",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "prepack": "node scripts/sync-version.mjs",
19
+ "postinstall": "node scripts/postinstall.mjs",
20
+ "build:vendor": "esbuild node_modules/justget/dist/browser/index.js --bundle --format=iife --global-name=justget --minify --outfile=static/vendor/justget.browser.js"
21
+ },
22
+ "dependencies": {
23
+ "justget": "^0.2.4"
24
+ },
25
+ "devDependencies": {
26
+ "esbuild": "^0.28.2"
27
+ },
28
+ "bugs": "https://github.com/aiti-ai/aiti-aigrdpwasm/issues"
29
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * postinstall: remove main.wasm (~11MB) from the installed copy.
3
+ *
4
+ * The npm tarball ships the wasm (that's what jsDelivr/unpkg serve from), but at
5
+ * runtime the page race-downloads it from CDNs via justget — no need to keep a
6
+ * local copy, so delete it right after install to save disk.
7
+ *
8
+ * Skipped when:
9
+ * - inside a source checkout (.git exists): running `npm install` in the repo
10
+ * triggers this lifecycle hook too; dev build artifacts must survive;
11
+ * - GRDPWASM_KEEP_WASM=1 is set explicitly.
12
+ */
13
+ import { existsSync, rmSync, statSync } from "node:fs";
14
+ import { dirname, join } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
18
+ const wasm = join(root, "static", "main.wasm");
19
+
20
+ if (process.env.GRDPWASM_KEEP_WASM === "1") {
21
+ console.log("[grdpwasm] GRDPWASM_KEEP_WASM=1, keep static/main.wasm");
22
+ } else if (existsSync(join(root, ".git"))) {
23
+ console.log("[grdpwasm] source checkout detected (.git), keep static/main.wasm");
24
+ } else if (existsSync(wasm)) {
25
+ const mb = (statSync(wasm).size / 1048576).toFixed(1);
26
+ rmSync(wasm);
27
+ console.log(`[grdpwasm] removed static/main.wasm (${mb} MB) - runtime fetches from CDN`);
28
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * prepack: sync the package.json version into PKG_VERSION inside
3
+ * static/index.html so the runtime CDN URLs always match the published version.
4
+ */
5
+ import { readFileSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
10
+ const { version } = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
11
+
12
+ const html = join(root, "static", "index.html");
13
+ const src = readFileSync(html, "utf8");
14
+ if (!/const PKG_VERSION = "[^"]*";/.test(src)) {
15
+ console.error("[grdpwasm] PKG_VERSION placeholder not found in index.html");
16
+ process.exit(1);
17
+ }
18
+ const next = src.replace(
19
+ /const PKG_VERSION = "[^"]*";/,
20
+ `const PKG_VERSION = "${version}";`
21
+ );
22
+ if (next !== src) writeFileSync(html, next);
23
+ console.log(`[grdpwasm] index.html PKG_VERSION -> ${version}`);