ai-remote 0.4.2 → 0.4.4

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 (75) hide show
  1. package/dist/index.js +1 -6
  2. package/dist/{protocols/index.d.ts → protocols.d.ts} +2 -0
  3. package/dist/protocols.js +24 -0
  4. package/package.json +15 -10
  5. package/dist/protocols/index.js +0 -34
  6. package/dist/protocols/rdp/buffer.d.ts +0 -38
  7. package/dist/protocols/rdp/buffer.js +0 -169
  8. package/dist/protocols/rdp/caps.d.ts +0 -49
  9. package/dist/protocols/rdp/caps.js +0 -259
  10. package/dist/protocols/rdp/cert.d.ts +0 -18
  11. package/dist/protocols/rdp/cert.js +0 -134
  12. package/dist/protocols/rdp/client.d.ts +0 -56
  13. package/dist/protocols/rdp/client.js +0 -1069
  14. package/dist/protocols/rdp/cliprdr.d.ts +0 -27
  15. package/dist/protocols/rdp/cliprdr.js +0 -239
  16. package/dist/protocols/rdp/credssp.d.ts +0 -34
  17. package/dist/protocols/rdp/credssp.js +0 -253
  18. package/dist/protocols/rdp/crypto.d.ts +0 -63
  19. package/dist/protocols/rdp/crypto.js +0 -368
  20. package/dist/protocols/rdp/display.d.ts +0 -38
  21. package/dist/protocols/rdp/display.js +0 -588
  22. package/dist/protocols/rdp/gcc.d.ts +0 -23
  23. package/dist/protocols/rdp/gcc.js +0 -131
  24. package/dist/protocols/rdp/keymap.d.ts +0 -9
  25. package/dist/protocols/rdp/keymap.js +0 -131
  26. package/dist/protocols/rdp/mcs.d.ts +0 -40
  27. package/dist/protocols/rdp/mcs.js +0 -222
  28. package/dist/protocols/rdp/ntlm.d.ts +0 -61
  29. package/dist/protocols/rdp/ntlm.js +0 -304
  30. package/dist/protocols/rdp/pdu.d.ts +0 -155
  31. package/dist/protocols/rdp/pdu.js +0 -443
  32. package/dist/protocols/rdp/rail.d.ts +0 -119
  33. package/dist/protocols/rdp/rail.js +0 -382
  34. package/dist/protocols/rdp/rle.d.ts +0 -13
  35. package/dist/protocols/rdp/rle.js +0 -275
  36. package/dist/protocols/rdp/sec.d.ts +0 -59
  37. package/dist/protocols/rdp/sec.js +0 -155
  38. package/dist/protocols/rdp/session.d.ts +0 -62
  39. package/dist/protocols/rdp/session.js +0 -306
  40. package/dist/protocols/rdp/tls.d.ts +0 -18
  41. package/dist/protocols/rdp/tls.js +0 -353
  42. package/dist/protocols/rdp/vchannel.d.ts +0 -28
  43. package/dist/protocols/rdp/vchannel.js +0 -58
  44. package/dist/protocols/rdp/x224.d.ts +0 -40
  45. package/dist/protocols/rdp/x224.js +0 -109
  46. package/dist/protocols/ssh/kex.d.ts +0 -97
  47. package/dist/protocols/ssh/kex.js +0 -176
  48. package/dist/protocols/ssh/messages.d.ts +0 -50
  49. package/dist/protocols/ssh/messages.js +0 -54
  50. package/dist/protocols/ssh/session.d.ts +0 -66
  51. package/dist/protocols/ssh/session.js +0 -608
  52. package/dist/protocols/ssh/terminal.d.ts +0 -46
  53. package/dist/protocols/ssh/terminal.js +0 -190
  54. package/dist/protocols/ssh/transport.d.ts +0 -62
  55. package/dist/protocols/ssh/transport.js +0 -612
  56. package/dist/protocols/ssh/wire.d.ts +0 -52
  57. package/dist/protocols/ssh/wire.js +0 -188
  58. package/dist/protocols/types.d.ts +0 -127
  59. package/dist/protocols/types.js +0 -0
  60. package/dist/protocols/vnc/input.d.ts +0 -5
  61. package/dist/protocols/vnc/input.js +0 -15
  62. package/dist/protocols/vnc/session.d.ts +0 -12
  63. package/dist/protocols/vnc/session.js +0 -258
  64. package/dist/shared/connection.d.ts +0 -81
  65. package/dist/shared/connection.js +0 -113
  66. package/dist/shared/device.d.ts +0 -26
  67. package/dist/shared/device.js +0 -0
  68. package/dist/shared/hosts.d.ts +0 -63
  69. package/dist/shared/hosts.js +0 -132
  70. package/dist/shared/icons.d.ts +0 -46
  71. package/dist/shared/icons.js +0 -41
  72. package/dist/shared/protocol.d.ts +0 -28
  73. package/dist/shared/protocol.js +0 -39
  74. package/dist/shared/signals.d.ts +0 -37
  75. package/dist/shared/signals.js +0 -41
@@ -1,188 +0,0 @@
1
- const textEncoder = new TextEncoder();
2
- const textDecoder = new TextDecoder();
3
- function encodeUtf8(text) {
4
- return textEncoder.encode(text);
5
- }
6
- function decodeUtf8(bytes) {
7
- return textDecoder.decode(bytes);
8
- }
9
- function concatBytes(...parts) {
10
- const total = parts.reduce((sum, part) => sum + part.length, 0);
11
- const out = new Uint8Array(total);
12
- let offset = 0;
13
- for (const part of parts) {
14
- out.set(part, offset);
15
- offset += part.length;
16
- }
17
- return out;
18
- }
19
- function bytesEqual(a, b) {
20
- if (a.length !== b.length) return false;
21
- let difference = 0;
22
- for (let index = 0; index < a.length; index++) difference |= (a[index] ?? 0) ^ (b[index] ?? 0);
23
- return difference === 0;
24
- }
25
- function toBase64(bytes) {
26
- let binary = "";
27
- for (const byte of bytes) binary += String.fromCharCode(byte);
28
- return btoa(binary);
29
- }
30
- function fromBase64(text) {
31
- const binary = atob(text);
32
- const bytes = new Uint8Array(binary.length);
33
- for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
34
- return bytes;
35
- }
36
- function toBase64Url(bytes) {
37
- return toBase64(bytes).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
38
- }
39
- class SshWriter {
40
- bytes;
41
- view;
42
- offset = 0;
43
- constructor(capacity = 256) {
44
- this.bytes = new Uint8Array(new ArrayBuffer(capacity));
45
- this.view = new DataView(this.bytes.buffer);
46
- }
47
- #ensure(extra) {
48
- if (this.offset + extra <= this.bytes.length) return;
49
- let capacity = Math.max(this.bytes.length * 2, 64);
50
- while (capacity < this.offset + extra) capacity *= 2;
51
- const grown = new Uint8Array(new ArrayBuffer(capacity));
52
- grown.set(this.bytes.subarray(0, this.offset));
53
- this.bytes = grown;
54
- this.view = new DataView(grown.buffer);
55
- }
56
- u8(value) {
57
- this.#ensure(1);
58
- this.view.setUint8(this.offset, value);
59
- this.offset += 1;
60
- return this;
61
- }
62
- boolean(value) {
63
- return this.u8(value ? 1 : 0);
64
- }
65
- u32(value) {
66
- this.#ensure(4);
67
- this.view.setUint32(this.offset, value >>> 0, false);
68
- this.offset += 4;
69
- return this;
70
- }
71
- raw(bytes) {
72
- this.#ensure(bytes.length);
73
- this.bytes.set(bytes, this.offset);
74
- this.offset += bytes.length;
75
- return this;
76
- }
77
- /** A length-prefixed string. Text is encoded as UTF-8. */
78
- string(value) {
79
- const bytes = typeof value === "string" ? encodeUtf8(value) : value;
80
- return this.u32(bytes.length).raw(bytes);
81
- }
82
- nameList(names) {
83
- return this.string(names.join(","));
84
- }
85
- /**
86
- * An mpint: two's complement, big endian, with no leading zero bytes except
87
- * the one that keeps a positive number from looking negative.
88
- */
89
- mpint(bytes) {
90
- let start = 0;
91
- while (start < bytes.length && bytes[start] === 0) start++;
92
- const magnitude = bytes.subarray(start);
93
- if (magnitude.length === 0) return this.u32(0);
94
- if ((magnitude[0] ?? 0) & 128) {
95
- this.u32(magnitude.length + 1).u8(0);
96
- return this.raw(magnitude);
97
- }
98
- return this.u32(magnitude.length).raw(magnitude);
99
- }
100
- get length() {
101
- return this.offset;
102
- }
103
- take() {
104
- return this.bytes.slice(0, this.offset);
105
- }
106
- }
107
- class SshReader {
108
- bytes;
109
- view;
110
- offset;
111
- constructor(bytes, offset = 0) {
112
- this.bytes = bytes;
113
- this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
114
- this.offset = offset;
115
- }
116
- get remaining() {
117
- return this.bytes.length - this.offset;
118
- }
119
- #need(count) {
120
- if (count < 0 || this.remaining < count) {
121
- throw new Error(`SSH: truncated packet (needed ${count} bytes, ${this.remaining} left)`);
122
- }
123
- }
124
- u8() {
125
- this.#need(1);
126
- return this.view.getUint8(this.offset++);
127
- }
128
- boolean() {
129
- return this.u8() !== 0;
130
- }
131
- u32() {
132
- this.#need(4);
133
- const value = this.view.getUint32(this.offset, false);
134
- this.offset += 4;
135
- return value;
136
- }
137
- raw(count) {
138
- this.#need(count);
139
- const slice = this.bytes.subarray(this.offset, this.offset + count);
140
- this.offset += count;
141
- return slice;
142
- }
143
- /** The bytes of a length-prefixed string. */
144
- stringBytes() {
145
- return this.raw(this.u32());
146
- }
147
- string() {
148
- return decodeUtf8(this.stringBytes());
149
- }
150
- nameList() {
151
- const value = this.string();
152
- return value ? value.split(",") : [];
153
- }
154
- /** An mpint's magnitude, with the sign byte removed. */
155
- mpint() {
156
- const bytes = this.stringBytes();
157
- let start = 0;
158
- while (start < bytes.length && bytes[start] === 0) start++;
159
- return bytes.subarray(start);
160
- }
161
- skip(count) {
162
- this.#need(count);
163
- this.offset += count;
164
- return this;
165
- }
166
- rest() {
167
- return this.bytes.subarray(this.offset);
168
- }
169
- }
170
- function padStart(bytes, width) {
171
- if (bytes.length === width) return bytes;
172
- if (bytes.length > width) return bytes.subarray(bytes.length - width);
173
- const out = new Uint8Array(width);
174
- out.set(bytes, width - bytes.length);
175
- return out;
176
- }
177
- export {
178
- SshReader,
179
- SshWriter,
180
- bytesEqual,
181
- concatBytes,
182
- decodeUtf8,
183
- encodeUtf8,
184
- fromBase64,
185
- padStart,
186
- toBase64,
187
- toBase64Url
188
- };
@@ -1,127 +0,0 @@
1
- /**
2
- * The interface every remote-desktop protocol presents to the UI.
3
- *
4
- * The workspace -- toolbar, stats rail, on-screen keyboard, reconnects -- does
5
- * not care whether a session is VNC or RDP. Everything that does differ is
6
- * reached through this interface instead of branching at each call site.
7
- */
8
- import type { ConnectionSettings, RemoteAppLaunch } from '../shared/connection';
9
- import type { RdpSecurity, RemoteProtocol } from '../shared/protocol';
10
- /** Where the pointer may go, in framebuffer pixels. */
11
- export interface ScreenGeometry {
12
- width: number;
13
- height: number;
14
- viewport: {
15
- x: number;
16
- y: number;
17
- width: number;
18
- height: number;
19
- };
20
- }
21
- /** Mouse buttons, as the UI names them. */
22
- export type PointerButton = 0 | 1 | 2;
23
- /** Wheel movement in discrete remote-protocol notches. Positive is right/down. */
24
- export interface ScrollDelta {
25
- x: number;
26
- y: number;
27
- }
28
- /**
29
- * A live session. Both drivers expose this, so the AI pane's screen tool and
30
- * the toolbar never have to ask which protocol they are driving.
31
- */
32
- export interface RemoteSession extends EventTarget {
33
- readonly protocol: RemoteProtocol;
34
- disconnect(): void;
35
- /** True once the desktop has been reached. */
36
- readonly connected: boolean;
37
- /** Fit the remote desktop to the pane rather than scrolling it. */
38
- scaleViewport: boolean;
39
- /** Watch without sending keyboard or pointer input. */
40
- viewOnly: boolean;
41
- /** RFB quality level. RDP ignores it. */
42
- qualityLevel?: number;
43
- /** RFB compression level. RDP ignores it. */
44
- compressionLevel?: number;
45
- /** The transport the metrics rail instruments. Can appear a tick after construction. */
46
- readonly socket?: WebSocket | null;
47
- readonly screenGeometry: ScreenGeometry;
48
- movePointer(x: number, y: number): boolean;
49
- clickPointer(x: number, y: number, button?: PointerButton): boolean;
50
- pointerButton(x: number, y: number, button?: PointerButton, pressed?: boolean): boolean;
51
- scrollPointer(x: number, y: number, delta: ScrollDelta): boolean;
52
- typeText(text: string): boolean;
53
- /** Prefer the protocol clipboard for longer text when it can paste reliably. */
54
- pasteText?(text: string): boolean | Promise<boolean>;
55
- /** Press or release one key by keysym. */
56
- sendKey(keysym: number, code: string | null, down?: boolean): void;
57
- /** Send text to the host's clipboard, when the protocol carries one. */
58
- sendClipboard?(text: string): void;
59
- /**
60
- * Stop taking keystrokes: the workspace has given the keyboard to something
61
- * local, such as the terminal or the assistant's message box.
62
- *
63
- * Both protocols listen on the element they paint into, so a key only leaves
64
- * for the host while that element holds focus. Handing focus back is the
65
- * whole mechanism -- plus, for RDP, letting go of anything still held down,
66
- * which would otherwise stay down on the far side.
67
- */
68
- releaseKeyboard?(): void;
69
- /** Ask the host for a different desktop size, when the protocol allows it. */
70
- requestDesktopSize?(width: number, height: number): void;
71
- /** The canvas the session paints into, for screenshots and thumbnails. */
72
- readonly canvas?: HTMLCanvasElement | null;
73
- }
74
- /** What the workspace hands a driver to open a session. */
75
- export interface SessionOptions {
76
- credentials: {
77
- username: string;
78
- password: string;
79
- };
80
- domain: string;
81
- security: RdpSecurity;
82
- remoteApp: RemoteAppLaunch | null;
83
- /** Desktop size to ask the host for. */
84
- width: number;
85
- height: number;
86
- /** False on a phone, where resizing the host would make a portrait strip of it. */
87
- allowRemoteResize: boolean;
88
- scaleViewport: boolean;
89
- viewOnly: boolean;
90
- quality: number;
91
- compression: number;
92
- /** Shown in this driver's own log lines. */
93
- hostLabel: string;
94
- }
95
- /** Wording for the one connect dialog, which is shared markup across protocols. */
96
- export interface DialogCopy {
97
- usernameLabel: string;
98
- usernamePlaceholder: string;
99
- usernameHelp: string;
100
- passwordLabel: string;
101
- passwordPlaceholder: string;
102
- passwordHelp: string;
103
- /** A VNC session needs some secret; RDP can sign in on the host's own screen. */
104
- requiresPassword: boolean;
105
- }
106
- /** Everything the UI knows about a close, before anyone tries to explain it. */
107
- export interface DisconnectContext {
108
- code?: number;
109
- reason: string;
110
- /** Pre-rendered ` (WebSocket 1006: ...)` tail, so wording stays consistent. */
111
- suffix: string;
112
- }
113
- /** One protocol implementation. */
114
- export interface ProtocolDriver {
115
- readonly protocol: RemoteProtocol;
116
- /** Used as the prefix of this driver's console lines. */
117
- readonly label: string;
118
- readonly defaultPort: number;
119
- readonly dialog: DialogCopy;
120
- /** The gateway endpoint for this connection, including any query it needs. */
121
- buildUrl(origin: string, settings: ConnectionSettings): string;
122
- createSession(container: HTMLElement, url: string, options: SessionOptions): RemoteSession;
123
- /** Extra detail worth logging once the first server frame has been parsed. */
124
- describeState(session: RemoteSession): Record<string, unknown>;
125
- /** Protocol-specific wording for a close, or null to fall back to the shared text. */
126
- describeDisconnect(context: DisconnectContext): string | null;
127
- }
File without changes
@@ -1,5 +0,0 @@
1
- /** RFB wheel buttons for a bounded two-axis scroll, in wire order. */
2
- export declare function vncWheelMasks(delta: {
3
- x: number;
4
- y: number;
5
- }): number[];
@@ -1,15 +0,0 @@
1
- function vncWheelMasks(delta) {
2
- const masks = [];
3
- const wheels = [
4
- { amount: delta.y, negativeMask: 1 << 3, positiveMask: 1 << 4 },
5
- { amount: delta.x, negativeMask: 1 << 5, positiveMask: 1 << 6 }
6
- ];
7
- for (const { amount, negativeMask, positiveMask } of wheels) {
8
- const mask = amount < 0 ? negativeMask : positiveMask;
9
- for (let step = 0; step < Math.min(20, Math.abs(amount)); step++) masks.push(mask);
10
- }
11
- return masks;
12
- }
13
- export {
14
- vncWheelMasks
15
- };
@@ -1,12 +0,0 @@
1
- /**
2
- * Everything VNC-specific, behind the driver interface the RDP side also
3
- * implements. noVNC's RFB object does the protocol work; this module decides
4
- * how it is addressed, constructed, controlled and explained.
5
- *
6
- * VNC splits the handshake differently from RDP: noVNC hands authentication to
7
- * the Worker, because macOS Screen Sharing needs the Apple Diffie-Hellman
8
- * (type 30) exchange that noVNC does not implement. That is why credentials
9
- * travel in the URL here and never do for RDP.
10
- */
11
- import type { ProtocolDriver } from '../types';
12
- export declare const vncDriver: ProtocolDriver;
@@ -1,258 +0,0 @@
1
- import RFB from "@novnc/novnc";
2
- import { DEFAULT_PORTS } from "../../shared/protocol.js";
3
- import {
4
- GATEWAY_KEEPALIVE_SIGNAL,
5
- GATEWAY_READY_SIGNAL,
6
- KEEPALIVE_INTERVAL_MS
7
- } from "../../shared/signals.js";
8
- import { vncWheelMasks } from "./input.js";
9
- const DIALOG = {
10
- usernameLabel: "Username",
11
- usernamePlaceholder: "macOS username (optional)",
12
- usernameHelp: "Use your macOS account name. Leave it empty when the VNC server only asks for a password.",
13
- passwordLabel: "Password",
14
- passwordPlaceholder: "Screen sharing password",
15
- passwordHelp: "Use the macOS account password, or the password configured for VNC screen sharing.",
16
- // A VNC session needs some secret; RDP can sign in on the host's own screen.
17
- requiresPassword: true
18
- };
19
- function buildUrl(origin, settings) {
20
- const query = new URLSearchParams({ host: settings.host, port: String(settings.port) });
21
- if (settings.username) query.set("vnc_user", settings.username);
22
- if (settings.password) query.set("vnc_pass", settings.password);
23
- return `${origin}/ws?${query}`;
24
- }
25
- const BUTTON_MASK = { 0: 1, 1: 2, 2: 4 };
26
- function eventDetail(event) {
27
- if (!("detail" in event)) return {};
28
- const detail = event.detail;
29
- if (!detail || typeof detail !== "object") return {};
30
- const copy = {};
31
- for (const [key, value] of Object.entries(detail)) copy[key] = value;
32
- return copy;
33
- }
34
- class VncSession extends EventTarget {
35
- protocol = "vnc";
36
- #rfb;
37
- #credentials;
38
- #transport = null;
39
- #keepalive = null;
40
- #readySent = false;
41
- #close = null;
42
- constructor(container, url, options) {
43
- super();
44
- this.#credentials = options.credentials;
45
- this.#rfb = new RFB(container, url, {
46
- credentials: {
47
- username: options.credentials.username,
48
- password: options.credentials.password
49
- },
50
- shared: true,
51
- wsProtocols: ["binary"]
52
- });
53
- this.#rfb._cursor?._canvas?.classList.add("remote-cursor-overlay");
54
- this.#rfb.showDotCursor = true;
55
- this.#rfb.resizeSession = options.allowRemoteResize;
56
- this.#rfb.clipViewport = true;
57
- this.#rfb.scaleViewport = options.scaleViewport;
58
- this.#rfb.viewOnly = options.viewOnly;
59
- this.#rfb.qualityLevel = options.quality;
60
- this.#rfb.compressionLevel = options.compression;
61
- this.#bindTransport();
62
- for (const name of ["connect", "disconnect", "credentialsrequired", "desktopname", "clipboard", "bell"]) {
63
- this.#rfb.addEventListener(name, (event) => {
64
- if (name === "credentialsrequired") {
65
- this.#rfb.sendCredentials({
66
- username: this.#credentials.username,
67
- password: this.#credentials.password
68
- });
69
- }
70
- const detail = eventDetail(event);
71
- if (name === "disconnect" && this.#close) {
72
- detail.code = this.#close.code;
73
- detail.reason = this.#close.reason;
74
- }
75
- this.dispatchEvent(new CustomEvent(name, { detail }));
76
- });
77
- }
78
- }
79
- /**
80
- * Attach to noVNC's WebSocket once it exists. Construction usually has it
81
- * already; a retry covers the tick where `_sock._websocket` is still null.
82
- */
83
- #bindTransport() {
84
- const attach = () => {
85
- const ws = this.#rfb._sock?._websocket ?? null;
86
- if (!ws || this.#transport === ws) return Boolean(ws);
87
- this.#transport = ws;
88
- const signalReady = () => {
89
- if (this.#readySent || ws.readyState !== WebSocket.OPEN) return;
90
- this.#readySent = true;
91
- ws.send(GATEWAY_READY_SIGNAL);
92
- };
93
- ws.addEventListener("open", signalReady);
94
- ws.addEventListener("close", (event) => {
95
- this.#close = { code: event.code, reason: event.reason ?? "" };
96
- this.#stopKeepalive();
97
- }, true);
98
- this.#keepalive = setInterval(() => {
99
- if (ws.readyState !== WebSocket.OPEN) return;
100
- try {
101
- ws.send(GATEWAY_KEEPALIVE_SIGNAL);
102
- const sock = this.#rfb._sock;
103
- if (sock && this.#rfb._rfbConnectionState === "connected") {
104
- RFB.messages.fbUpdateRequest(sock, true, 0, 0, 1, 1);
105
- }
106
- } catch {
107
- }
108
- }, KEEPALIVE_INTERVAL_MS);
109
- signalReady();
110
- return true;
111
- };
112
- if (attach()) return;
113
- let tries = 0;
114
- const timer = setInterval(() => {
115
- if (attach() || ++tries > 20) clearInterval(timer);
116
- }, 25);
117
- }
118
- #stopKeepalive() {
119
- if (this.#keepalive) clearInterval(this.#keepalive);
120
- this.#keepalive = null;
121
- }
122
- get connected() {
123
- return this.#rfb._rfbConnectionState === "connected";
124
- }
125
- get socket() {
126
- return this.#rfb._sock?._websocket ?? null;
127
- }
128
- get canvas() {
129
- return this.#rfb._canvas;
130
- }
131
- get scaleViewport() {
132
- return this.#rfb.scaleViewport;
133
- }
134
- set scaleViewport(enabled) {
135
- this.#rfb.scaleViewport = enabled;
136
- }
137
- get viewOnly() {
138
- return this.#rfb.viewOnly;
139
- }
140
- set viewOnly(enabled) {
141
- this.#rfb.viewOnly = enabled;
142
- }
143
- get qualityLevel() {
144
- return this.#rfb.qualityLevel;
145
- }
146
- set qualityLevel(level) {
147
- this.#rfb.qualityLevel = level;
148
- }
149
- get compressionLevel() {
150
- return this.#rfb.compressionLevel;
151
- }
152
- set compressionLevel(level) {
153
- this.#rfb.compressionLevel = level;
154
- }
155
- get screenGeometry() {
156
- const width = this.#rfb._fbWidth || 0;
157
- const height = this.#rfb._fbHeight || 0;
158
- return { width, height, viewport: { x: 0, y: 0, width, height } };
159
- }
160
- /** Internal state worth logging once the first server frame has been parsed. */
161
- describeState() {
162
- const socket = this.#rfb._sock;
163
- return {
164
- connectionState: this.#rfb._rfbConnectionState,
165
- initializationState: this.#rfb._rfbInitState,
166
- queuedReceiveBytes: Math.max(0, (socket?._rQlen ?? 0) - (socket?._rQi ?? 0))
167
- };
168
- }
169
- disconnect() {
170
- this.#stopKeepalive();
171
- this.#rfb.disconnect();
172
- }
173
- sendKey(keysym, code, down) {
174
- this.#rfb.sendKey(keysym, code, down);
175
- }
176
- sendClipboard(text) {
177
- this.#rfb.clipboardPasteFrom(text);
178
- }
179
- releaseKeyboard() {
180
- this.#rfb.blur();
181
- }
182
- /**
183
- * noVNC keeps pointer input private -- its own handlers translate browser
184
- * events into framebuffer coordinates first -- so this reaches for the same
185
- * wire helper `_sendMouse` ends at, with coordinates already in pixels.
186
- */
187
- #pointer(x, y, mask) {
188
- const socket = this.#rfb._sock;
189
- if (!socket || this.#rfb._rfbConnectionState !== "connected" || this.#rfb.viewOnly) return false;
190
- const width = this.#rfb._fbWidth || 1;
191
- const height = this.#rfb._fbHeight || 1;
192
- RFB.messages.pointerEvent(
193
- socket,
194
- Math.max(0, Math.min(width - 1, Math.round(x))),
195
- Math.max(0, Math.min(height - 1, Math.round(y))),
196
- mask
197
- );
198
- return true;
199
- }
200
- movePointer(x, y) {
201
- return this.#pointer(x, y, 0);
202
- }
203
- clickPointer(x, y, button = 0) {
204
- if (!this.#pointer(x, y, 0)) return false;
205
- this.#pointer(x, y, BUTTON_MASK[button]);
206
- this.#pointer(x, y, 0);
207
- return true;
208
- }
209
- pointerButton(x, y, button = 0, pressed = true) {
210
- return this.#pointer(x, y, pressed ? BUTTON_MASK[button] : 0);
211
- }
212
- scrollPointer(x, y, delta) {
213
- for (const mask of vncWheelMasks(delta)) {
214
- if (!this.#pointer(x, y, mask)) return false;
215
- if (!this.#pointer(x, y, 0)) return false;
216
- }
217
- return true;
218
- }
219
- /**
220
- * VNC has no Unicode input message, so text is typed as keysyms. For every
221
- * character in the Basic Multilingual Plane the keysym is the code point.
222
- */
223
- typeText(text) {
224
- if (this.#rfb._rfbConnectionState !== "connected" || this.#rfb.viewOnly || !text) return false;
225
- for (const character of text) {
226
- const point = character.codePointAt(0) ?? 0;
227
- const keysym = character === "\n" ? 65293 : character === " " ? 65289 : point;
228
- this.#rfb.sendKey(keysym, null, true);
229
- this.#rfb.sendKey(keysym, null, false);
230
- }
231
- return true;
232
- }
233
- }
234
- function describeDisconnect({ code, reason, suffix }) {
235
- if (code === 4001 || /auth|password|credential/i.test(reason)) {
236
- return `VNC authentication failed${suffix}. Verify the macOS account username and its password.`;
237
- }
238
- if (code === 4002 || /apple dh/i.test(reason)) {
239
- return `macOS VNC authentication handshake failed${suffix}.`;
240
- }
241
- if (code === 1011 || /cannot connect|unreachable|tcp error|tcp write/i.test(reason)) {
242
- return `The Worker could not keep a TCP connection to the VNC host${suffix}. Check that Screen Sharing is running and listening on this address.`;
243
- }
244
- return null;
245
- }
246
- const vncDriver = {
247
- protocol: "vnc",
248
- label: "VNC Client",
249
- defaultPort: DEFAULT_PORTS.vnc,
250
- dialog: DIALOG,
251
- buildUrl,
252
- createSession: (container, url, options) => new VncSession(container, url, options),
253
- describeState: (session) => session instanceof VncSession ? session.describeState() : {},
254
- describeDisconnect
255
- };
256
- export {
257
- vncDriver
258
- };
@@ -1,81 +0,0 @@
1
- /**
2
- * One connection, described once.
3
- *
4
- * Every way of starting a session -- a saved card on the welcome screen, an
5
- * address typed by hand, a RemoteApp launcher, a reconnect after a drop --
6
- * produces exactly this shape and hands it to exactly one dialog. That is the
7
- * whole point of the type: before it existed, a saved card could only ask for a
8
- * username and a password, so its port, security layer, domain and picture
9
- * quality were unreachable once the card had been made.
10
- */
11
- import { type RdpSecurity, type RemoteProtocol } from './protocol';
12
- /** A published Windows application launched instead of the full desktop. */
13
- export interface RemoteAppLaunch {
14
- /** Alias (`||notepad`) or an executable path. */
15
- program: string;
16
- workingDir?: string;
17
- arguments?: string;
18
- }
19
- /** The companion terminal offered beside a desktop. */
20
- export interface SshCompanion {
21
- enabled: boolean;
22
- port: number;
23
- username: string;
24
- }
25
- /** Picture and input options. VNC negotiates them; RDP and SSH ignore what does not apply. */
26
- export interface DisplaySettings {
27
- /** RFB quality level, 1 (smallest) to 9 (sharpest). */
28
- quality: number;
29
- /** RFB compression level, 0 (fastest) to 9 (smallest). */
30
- compression: number;
31
- /** Fit the remote desktop to the pane rather than scrolling it. */
32
- scaling: boolean;
33
- /** Watch without sending keyboard or pointer input. */
34
- viewOnly: boolean;
35
- }
36
- /**
37
- * Everything a session needs. `password` is the only field never persisted
38
- * anywhere -- not in the device library on the gateway, not in localStorage.
39
- */
40
- export interface ConnectionSettings {
41
- /** Stable identity: protocol, address, port and program. See {@link connectionId}. */
42
- id: string;
43
- label: string;
44
- protocol: RemoteProtocol;
45
- host: string;
46
- port: number;
47
- username: string;
48
- password: string;
49
- /** Windows domain or computer name. RDP only. */
50
- domain: string;
51
- security: RdpSecurity;
52
- remoteApp: RemoteAppLaunch | null;
53
- display: DisplaySettings;
54
- ssh: SshCompanion;
55
- /** Open the terminal panel as soon as the desktop appears. */
56
- openSshOnConnect: boolean;
57
- }
58
- export declare const DEFAULT_DISPLAY_SETTINGS: DisplaySettings;
59
- /**
60
- * A connection's identity is what it points at, not when it was made. Editing
61
- * the address therefore makes a different connection rather than silently
62
- * repointing an existing card, which is the behaviour people expect from a
63
- * list of machines.
64
- */
65
- export declare function connectionId(input: Pick<ConnectionSettings, 'protocol' | 'host' | 'port'> & {
66
- remoteApp?: RemoteAppLaunch | null;
67
- }): string;
68
- /** A blank connection, ready for the dialog to fill in. */
69
- export declare function emptyConnection(protocol?: RemoteProtocol): ConnectionSettings;
70
- export declare function normalizeDisplaySettings(value: unknown): DisplaySettings;
71
- /**
72
- * Build a complete, valid connection out of whatever a caller happens to have.
73
- * Partial input from a saved card, a host list entry, a URL or localStorage all
74
- * arrive here, so the dialog never has to reason about missing fields.
75
- */
76
- export declare function normalizeConnection(input: Partial<ConnectionSettings> & Record<string, unknown>): ConnectionSettings;
77
- /** What a connection is worth remembering by -- never the password. */
78
- export type PersistedConnection = Omit<ConnectionSettings, 'password'>;
79
- export declare function withoutPassword(settings: ConnectionSettings): PersistedConnection;
80
- /** A one-line description for the dialog's summary row and the status bar. */
81
- export declare function describeConnection(settings: ConnectionSettings): string;