@aiti-ai/grdpwasm 0.0.0-stage → 0.2.1

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,162 @@
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**: current releases (`0.2.x`) are on the private registry —
14
+ > `npm install @aiti-ai/grdpwasm --registry https://npm.alot.pw`. npmjs.org publication is
15
+ > prepared (`.github/workflows/publish-npm.yml`, two commands once NPM_TOKEN is set);
16
+ > jsDelivr/unpkg CDN links become live at that point. Until then the wasm loads from the
17
+ > same origin when served behind a backend (e.g. aiti-ui-env's rdp-bridge).
18
+
19
+ ## Updates in this fork
20
+
21
+ ### xrdp / gnome-remote-desktop compatibility
22
+
23
+ - Vendored patched `grdp` fork (`third_party/grdp`): planar codec constant fix, capability
24
+ advertisement trimmed to what this client can honor (no SURFACE_COMMANDS, no frame
25
+ marker, no bitmap-cache REV2), NSCodec-capture server-type detection, fullscreen
26
+ refresh recovery, ECDSA CredSSP key echo, fast-path input PDU padding, clear NLA errors.
27
+ - Automatic EGFX negotiation: connects with GFX first; if the server's actual codec is
28
+ undecodable (xrdp sends progressive/planar only), it reconnects once in compat mode.
29
+ GDS/Windows (H.264) connect normally.
30
+ - Lossless WASM transport: inbound messages queue without blocking the JS thread or
31
+ dropping data (dropping caused stream desync → artifacting/disconnects); bursts are
32
+ bounded by the bridge's backpressure.
33
+
34
+ ### Frontend
35
+
36
+ - **fixedTarget mode**: the target host/port is provided by the server (injected
37
+ `__rdpTarget`) or `?fixedTarget=true`, and can no longer be tampered with from the
38
+ frontend; standalone mode keeps free-form connection fields.
39
+ - Virtual keyboard (programmer layout), shortcut bar, native system-keyboard proxy,
40
+ X11/RFB keysym helpers — shared, host-agnostic components.
41
+ - xrdp color-cursor mitigation: cursors whose AND mask makes them fully opaque render
42
+ as black blocks; they are detected (≥95% opaque ratio) and discarded.
43
+
44
+ ### Packaging & delivery (@aiti-ai/grdpwasm)
45
+
46
+ - Published as an npm package. The tarball ships `main.wasm` (that's what jsDelivr/unpkg
47
+ serve from), but `postinstall` removes the local copy after install — at runtime the
48
+ page race-downloads the wasm from CDNs via [justget](https://github.com/justdb/justget)
49
+ with same-origin fallback (override: `?wasm=<url>` or `localStorage['grdpwasm.wasmUrl']`).
50
+ - Server-side fallback for embedders: a backend helper (`ensureRdpWasm` pattern) can
51
+ pre-fetch the wasm into a cache dir so the static server always has one to serve.
52
+
53
+ ## Architecture
54
+
55
+ ```
56
+ Browser (WASM) ──WebSocket──► proxy (Go) ──TCP──► RDP Server
57
+ ```
58
+
59
+ Because browsers cannot open raw TCP sockets, a lightweight Go proxy server bridges
60
+ WebSocket connections from the browser to the RDP server's TCP port.
61
+
62
+ ## Requirements
63
+
64
+ - Go 1.24 or later
65
+ - A reachable RDP server (Windows or any RDP-compatible host)
66
+
67
+ ## Build
68
+
69
+ ```bash
70
+ git clone https://github.com/nakagami/grdpwasm.git
71
+ cd grdpwasm
72
+ make all
73
+ ```
74
+
75
+ `make all` produces:
76
+
77
+ | Output | Description |
78
+ |---|---|
79
+ | `static/main.wasm` | Go WASM binary (runs in the browser) |
80
+ | `static/wasm_exec.js` | Go runtime JS support file |
81
+ | `proxy/proxy` | WebSocket-to-TCP proxy + static file server |
82
+
83
+ ## Run
84
+
85
+ ```bash
86
+ make serve
87
+ # or equivalently:
88
+ ./proxy/proxy -listen :8080 -static static
89
+ ```
90
+
91
+ Then open **http://localhost:8080** in your browser.
92
+
93
+ ### Proxy options
94
+
95
+ | Flag | Default | Description |
96
+ |---|---|---|
97
+ | `-listen` | `:8080` | Address and port to listen on |
98
+ | `-static` | `static` | Directory to serve static files from |
99
+
100
+ ## Usage
101
+
102
+ 1. Open `http://localhost:8080` in a browser.
103
+ 2. Fill in the connection form:
104
+ - **Host** — hostname or IP address of the RDP server
105
+ - **Port** — RDP port (default `3389`)
106
+ - **Domain** — Windows domain (leave blank for local accounts)
107
+ - **User** — username
108
+ - **Password** — password
109
+ - **Width / Height** — initial desktop resolution
110
+ 3. Click **Connect**.
111
+ 4. The remote desktop appears in the canvas. Click the canvas to capture keyboard focus.
112
+ 5. Click **Disconnect** to end the session.
113
+
114
+ ## Keyboard & Mouse
115
+
116
+ All standard keyboard input is forwarded to the remote desktop via RDP scan codes.
117
+ Mouse move, button clicks, and scroll wheel are fully supported.
118
+
119
+ > **Note:** The browser tab must have focus for keyboard events to be forwarded.
120
+ > Click inside the canvas area if keys stop responding.
121
+
122
+ ## Audio
123
+
124
+ Remote audio is streamed via RDPSND and played through the browser's Web Audio API
125
+ (PCM 44100 Hz, stereo, 16-bit signed little-endian).
126
+
127
+ ## Security notes
128
+
129
+ - The proxy accepts connections from any origin. Run it only on a trusted network
130
+ or add authentication before exposing it to the internet.
131
+ - Credentials are transmitted from the browser to the proxy over WebSocket.
132
+ Use HTTPS/WSS (put the proxy behind a TLS-terminating reverse proxy such as
133
+ nginx or Caddy) when accessing it over an untrusted network.
134
+
135
+ ## Development
136
+
137
+ ```
138
+ npm install # install JS dependencies (justget, esbuild)
139
+ make wasm # rebuild only the WASM binary
140
+ make proxy # rebuild only the proxy server
141
+ make wasm_exec # refresh wasm_exec.js from the local Go toolchain
142
+ npm run build:vendor # rebuild static/vendor/justget.browser.js from the installed justget
143
+ make clean # remove all build artifacts
144
+ ```
145
+
146
+ ## npm package
147
+
148
+ Publish the browser assets as `@aiti-ai/grdpwasm`. Consumers get everything except
149
+ `main.wasm` — the page fetches it from CDN at runtime.
150
+
151
+ ```bash
152
+ npm run build:vendor # bundle justget/browser into static/vendor/
153
+ make all # build main.wasm + wasm_exec.js + proxy
154
+ npm publish # prepack syncs the version into index.html; tarball ships the wasm
155
+ ```
156
+
157
+ Consumers get `static/` (html/js/vendor) without `main.wasm` — the page fetches it
158
+ from CDN at runtime. Set `GRDPWASM_KEEP_WASM=1` before install to keep the local copy.
159
+
160
+ ## License
161
+
162
+ 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.1",
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}`);