tabby-rdp 0.2.7 → 0.3.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +72 -2
  2. package/README.md +182 -21
  3. package/dist/connectionStatus.js +140 -0
  4. package/dist/connectionStatus.js.map +1 -0
  5. package/dist/desk.js +21 -10
  6. package/dist/desk.js.map +1 -1
  7. package/dist/deskScript.js +3 -0
  8. package/dist/deskScript.js.map +1 -1
  9. package/dist/desktop.service.js +994 -53
  10. package/dist/desktop.service.js.map +1 -1
  11. package/dist/desktopForm.js +64 -19
  12. package/dist/desktopForm.js.map +1 -1
  13. package/dist/desktops.js +60 -21
  14. package/dist/desktops.js.map +1 -1
  15. package/dist/fileTransfer.js +152 -5
  16. package/dist/fileTransfer.js.map +1 -1
  17. package/dist/header.js +22 -7
  18. package/dist/header.js.map +1 -1
  19. package/dist/help.js +241 -0
  20. package/dist/help.js.map +1 -0
  21. package/dist/index.js +16 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/keyboard.js +142 -5
  24. package/dist/keyboard.js.map +1 -1
  25. package/dist/microphone.js +295 -0
  26. package/dist/microphone.js.map +1 -0
  27. package/dist/osd.js +79 -0
  28. package/dist/osd.js.map +1 -0
  29. package/dist/probe.js +33 -0
  30. package/dist/probe.js.map +1 -0
  31. package/dist/rdcleanpath.js +132 -7
  32. package/dist/rdcleanpath.js.map +1 -1
  33. package/dist/rdpFile.js +47 -0
  34. package/dist/rdpFile.js.map +1 -0
  35. package/dist/rdpProfile.js +367 -0
  36. package/dist/rdpProfile.js.map +1 -0
  37. package/dist/remoteSetup.js +90 -11
  38. package/dist/remoteSetup.js.map +1 -1
  39. package/dist/settingsPage.js +554 -0
  40. package/dist/settingsPage.js.map +1 -0
  41. package/dist/signin.js +21 -2
  42. package/dist/signin.js.map +1 -1
  43. package/dist/ssh.js +28 -0
  44. package/dist/ssh.js.map +1 -1
  45. package/dist/targets.js +148 -8
  46. package/dist/targets.js.map +1 -1
  47. package/dist/ui.js +163 -24
  48. package/dist/ui.js.map +1 -1
  49. package/dist/updates.js +123 -0
  50. package/dist/updates.js.map +1 -0
  51. package/dist/vms.js +90 -0
  52. package/dist/vms.js.map +1 -0
  53. package/dist/wake.js +124 -0
  54. package/dist/wake.js.map +1 -0
  55. package/package.json +2 -1
  56. package/vendor/IRONRDP-LICENSE +3 -3
  57. package/vendor/iron-remote-desktop-rdp.js +698 -451
  58. package/vendor/iron-remote-desktop.js +87 -81
  59. package/vendor/ironrdp_web_bg.wasm +0 -0
package/dist/wake.js ADDED
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseWake = parseWake;
4
+ exports.wakeFromText = wakeFromText;
5
+ exports.rdpAnswers = rdpAnswers;
6
+ exports.wakeDesktop = wakeDesktop;
7
+ exports.waitForRdp = waitForRdp;
8
+ const deskScript_1 = require("./deskScript");
9
+ const MAC = /^[0-9a-f]{2}([:-]?)[0-9a-f]{2}(\1[0-9a-f]{2}){4}$/i;
10
+ const IPV4 = /^\d{1,3}(\.\d{1,3}){3}$/;
11
+ /** A `wake` config value, checked; undefined when absent or unusable. */
12
+ function parseWake(raw) {
13
+ if (typeof raw?.vm === 'string' && raw.vm.trim()) {
14
+ return { vm: raw.vm.trim() };
15
+ }
16
+ const mac = typeof raw?.mac === 'string' ? raw.mac.trim() : '';
17
+ if (MAC.test(mac)) {
18
+ const port = Number(raw.port ?? 9);
19
+ return {
20
+ mac,
21
+ ...typeof raw.broadcast === 'string' && IPV4.test(raw.broadcast.trim()) ? { broadcast: raw.broadcast.trim() } : {},
22
+ ...Number.isInteger(port) && port > 0 && port < 65536 && port !== 9 ? { port } : {},
23
+ };
24
+ }
25
+ return undefined;
26
+ }
27
+ /** The add-desktop form's wake field: a MAC address means Wake-on-LAN, anything else names a libvirt VM. */
28
+ function wakeFromText(text) {
29
+ const value = text.trim();
30
+ return !value ? undefined : MAC.test(value) ? { mac: value } : { vm: value };
31
+ }
32
+ // Whether an RDP server answers at $H:$P, as seen from the SSH host: an X.224 connection request must get a TPKT
33
+ // reply, since a port can accept connections before the server behind it does (QEMU's user-mode port forwarding,
34
+ // for one). Without python3, an open TCP port will do.
35
+ const PROBE_SCRIPT = String.raw `
36
+ if command -v python3 >/dev/null 2>&1; then
37
+ python3 -c '
38
+ import socket, sys
39
+ try:
40
+ s = socket.create_connection((sys.argv[1], int(sys.argv[2])), 3)
41
+ s.settimeout(3)
42
+ s.sendall(bytes.fromhex("030000130ee000000000000100080003000000"))
43
+ print("RD_OPEN" if s.recv(2) == b"\x03\x00" else "RD_CLOSED")
44
+ except Exception:
45
+ print("RD_CLOSED")
46
+ ' "$H" "$P"
47
+ elif command -v nc >/dev/null 2>&1; then
48
+ nc -z -w 3 "$H" "$P" >/dev/null 2>&1 && echo RD_OPEN || echo RD_CLOSED
49
+ else
50
+ timeout 3 bash -c 'exec 3<>"/dev/tcp/$0/$1"' "$H" "$P" >/dev/null 2>&1 && echo RD_OPEN || echo RD_CLOSED
51
+ fi
52
+ `;
53
+ // Starts (or resumes) the libvirt domain $VM as the SSH user: the system instance first, where VMs usually are
54
+ // (needs the libvirt group or polkit), then the user's own session instance.
55
+ const VM_SCRIPT = String.raw `
56
+ command -v virsh >/dev/null 2>&1 || { echo "RD_ERR virsh is not installed on the SSH host"; exit 0; }
57
+ errors=""
58
+ for uri in qemu:///system qemu:///session; do
59
+ if ! state=$(virsh -c "$uri" domstate "$VM" 2>&1); then
60
+ errors="$errors${"$"}{errors:+; }$uri: $(printf '%s' "$state" | tr '\n' ' ' | sed 's/^error: //' | cut -c1-160)"
61
+ continue
62
+ fi
63
+ case $(printf '%s\n' "$state" | head -n 1) in
64
+ running) echo "RD_OK $VM is already running ($uri)"; exit 0 ;;
65
+ paused) how=resumed; out=$(virsh -c "$uri" resume "$VM" 2>&1) ;;
66
+ pmsuspended) how="woken up"; out=$(virsh -c "$uri" dompmwakeup "$VM" 2>&1) ;;
67
+ *) how=started; out=$(virsh -c "$uri" start "$VM" 2>&1) ;;
68
+ esac && { echo "RD_OK $VM $how ($uri)"; exit 0; }
69
+ echo "RD_ERR virsh: $(printf '%s' "$out" | tr '\n' ' ' | sed 's/^error: //' | cut -c1-240)"
70
+ exit 0
71
+ done
72
+ echo "RD_ERR no libvirt VM named $VM for this user ($errors)"
73
+ `;
74
+ // A Wake-on-LAN magic packet ($MAC 16 times after 6 bytes of 0xff) to $BCAST:$PORT, with the standard library.
75
+ const WOL_SCRIPT = String.raw `
76
+ command -v python3 >/dev/null 2>&1 || { echo "RD_ERR sending Wake-on-LAN needs python3 on the SSH host"; exit 0; }
77
+ python3 -c '
78
+ import socket, sys
79
+ mac = bytes.fromhex(sys.argv[1].replace(":", "").replace("-", ""))
80
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
81
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
82
+ s.sendto(b"\xff" * 6 + mac * 16, (sys.argv[2], int(sys.argv[3])))
83
+ print("RD_OK Wake-on-LAN sent to %s via %s:%s" % tuple(sys.argv[1:4]))
84
+ ' "$MAC" "$BCAST" "$PORT" 2>&1 || echo "RD_ERR could not send the Wake-on-LAN packet"
85
+ `;
86
+ /** Whether the RDP server at host:port answers, as seen from the SSH host. */
87
+ async function rdpAnswers(target, host, port) {
88
+ const out = await target.exec('sh -s', `H=${(0, deskScript_1.shq)(host)}\nP=${port}\n${PROBE_SCRIPT}`);
89
+ return /^RD_OPEN$/m.test(out);
90
+ }
91
+ /** Starts the desktop as `wake` says. Resolves with what was done; throws with the reason when it couldn't. */
92
+ async function wakeDesktop(target, wake) {
93
+ const script = 'vm' in wake
94
+ ? `VM=${(0, deskScript_1.shq)(wake.vm)}\n${VM_SCRIPT}`
95
+ : `MAC=${(0, deskScript_1.shq)(wake.mac)}\nBCAST=${(0, deskScript_1.shq)(wake.broadcast ?? '255.255.255.255')}\nPORT=${wake.port ?? 9}\n${WOL_SCRIPT}`;
96
+ const out = await target.exec('sh -s', script);
97
+ const err = /^RD_ERR (.*)$/m.exec(out);
98
+ if (err) {
99
+ throw new Error(err[1].trim());
100
+ }
101
+ return /^RD_OK (.*)$/m.exec(out)?.[1] ?? 'no result';
102
+ }
103
+ /**
104
+ * Probes until the RDP server answers (true), the time is up or `stopped()` (false). At most one probe every
105
+ * 3 s; `progress` is called every 5 s.
106
+ */
107
+ async function waitForRdp(target, host, port, timeoutMs, stopped, progress) {
108
+ const deadline = Date.now() + timeoutMs;
109
+ const ticker = setInterval(() => stopped() || progress(), 5000);
110
+ try {
111
+ while (Date.now() < deadline && !stopped()) {
112
+ const started = Date.now();
113
+ if (await rdpAnswers(target, host, port).catch(() => false)) {
114
+ return !stopped();
115
+ }
116
+ await new Promise(resolve => setTimeout(resolve, Math.max(0, 3000 - (Date.now() - started))));
117
+ }
118
+ return false;
119
+ }
120
+ finally {
121
+ clearInterval(ticker);
122
+ }
123
+ }
124
+ //# sourceMappingURL=wake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wake.js","sourceRoot":"","sources":["../src/wake.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tabby-rdp",
3
- "version": "0.2.7",
3
+ "version": "0.3.1",
4
4
  "description": "Remote desktops in Tabby's SSH tabs: Linux (GNOME) and Windows, through the SSH connection, with clipboard, files and sound. Built on IronRDP.",
5
5
  "keywords": [
6
6
  "tabby-plugin",
@@ -49,6 +49,7 @@
49
49
  "@types/node": "^20.19.0",
50
50
  "rxjs": "7.8.2",
51
51
  "tabby-core": "1.0.231-nightly.0",
52
+ "tabby-settings": "1.0.231-nightly.0",
52
53
  "tabby-terminal": "1.0.231-nightly.0",
53
54
  "typescript": "~5.9.3"
54
55
  }
@@ -1,6 +1,6 @@
1
- vendor/iron-remote-desktop*.js are built from IronRDP (https://github.com/Devolutions/IronRDP) at commit
2
- 9b151c4c2e47c6014e1e8e55909d4180aa8bdb99 with the patches in ironrdp/patches applied. IronRDP is dual-licensed under MIT or Apache-2.0;
3
- its MIT license follows.
1
+ vendor/iron-remote-desktop*.js and vendor/ironrdp_web_bg.wasm are built from IronRDP
2
+ (https://github.com/Devolutions/IronRDP) at commit 9b151c4c2e47c6014e1e8e55909d4180aa8bdb99 with the patches in
3
+ ironrdp/patches applied. IronRDP is dual-licensed under MIT or Apache-2.0; its MIT license follows.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any
6
6
  person obtaining a copy of this software and associated