@anvia/react-ui 1.3.0 → 1.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 (131) hide show
  1. package/dist/attachment/index.d.ts +3 -21
  2. package/dist/attachment/index.js +3 -12
  3. package/dist/attachment-DsKTPumt.js +89 -0
  4. package/dist/attachment-DsKTPumt.js.map +1 -0
  5. package/dist/chat/index.d.ts +3 -152
  6. package/dist/chat/index.js +3 -25
  7. package/dist/chat-CAnvUEAv.js +1210 -0
  8. package/dist/chat-CAnvUEAv.js.map +1 -0
  9. package/dist/completion/index.d.ts +3 -29
  10. package/dist/completion/index.js +3 -16
  11. package/dist/completion-DDTKoZRJ.js +124 -0
  12. package/dist/completion-DDTKoZRJ.js.map +1 -0
  13. package/dist/connection-DeNq8wHS.js +203 -0
  14. package/dist/connection-DeNq8wHS.js.map +1 -0
  15. package/dist/contexts-B4ijG24z.js +212 -0
  16. package/dist/contexts-B4ijG24z.js.map +1 -0
  17. package/dist/elements-D4vmkkF8.js +19 -0
  18. package/dist/elements-D4vmkkF8.js.map +1 -0
  19. package/dist/graph-explorer/index.d.ts +54 -73
  20. package/dist/graph-explorer/index.d.ts.map +1 -0
  21. package/dist/graph-explorer/index.js +181 -248
  22. package/dist/graph-explorer/index.js.map +1 -1
  23. package/dist/human-input/index.d.ts +3 -78
  24. package/dist/human-input/index.js +3 -21
  25. package/dist/human-input-CsCbZomZ.js +314 -0
  26. package/dist/human-input-CsCbZomZ.js.map +1 -0
  27. package/dist/image/index.d.ts +3 -40
  28. package/dist/image/index.js +3 -12
  29. package/dist/image-D_7vaQCR.js +302 -0
  30. package/dist/image-D_7vaQCR.js.map +1 -0
  31. package/dist/index-B1Qs2Cjv.d.ts +145 -0
  32. package/dist/index-B1Qs2Cjv.d.ts.map +1 -0
  33. package/dist/index-BCBVcl0D.d.ts +277 -0
  34. package/dist/index-BCBVcl0D.d.ts.map +1 -0
  35. package/dist/index-CE22tsua.d.ts +20 -0
  36. package/dist/index-CE22tsua.d.ts.map +1 -0
  37. package/dist/index-CGqtpc6b.d.ts +25 -0
  38. package/dist/index-CGqtpc6b.d.ts.map +1 -0
  39. package/dist/index-DqdyM0Jm.d.ts +7 -0
  40. package/dist/index-DqdyM0Jm.d.ts.map +1 -0
  41. package/dist/index-Mw3ltwSG.d.ts +318 -0
  42. package/dist/index-Mw3ltwSG.d.ts.map +1 -0
  43. package/dist/index.d.ts +7 -40
  44. package/dist/index.js +34 -118
  45. package/dist/index.js.map +1 -1
  46. package/dist/message/index.d.ts +3 -105
  47. package/dist/message/index.js +3 -20
  48. package/dist/message-Dn3wW6yy.js +783 -0
  49. package/dist/message-Dn3wW6yy.js.map +1 -0
  50. package/dist/primitives-xfE7zfwJ.js +15 -0
  51. package/dist/primitives-xfE7zfwJ.js.map +1 -0
  52. package/dist/refs-Djj9rSvB.js +10 -0
  53. package/dist/refs-Djj9rSvB.js.map +1 -0
  54. package/dist/sandbox-desktop/index.d.ts +100 -104
  55. package/dist/sandbox-desktop/index.d.ts.map +1 -0
  56. package/dist/sandbox-desktop/index.js +344 -342
  57. package/dist/sandbox-desktop/index.js.map +1 -1
  58. package/dist/sandbox-terminal/index.d.ts +52 -56
  59. package/dist/sandbox-terminal/index.d.ts.map +1 -0
  60. package/dist/sandbox-terminal/index.js +217 -208
  61. package/dist/sandbox-terminal/index.js.map +1 -1
  62. package/dist/selection-toolbar/index.d.ts +3 -21
  63. package/dist/selection-toolbar/index.js +3 -13
  64. package/dist/selection-toolbar-CIkOoM-7.js +171 -0
  65. package/dist/selection-toolbar-CIkOoM-7.js.map +1 -0
  66. package/dist/shared-BSYElFU2.d.ts +2 -0
  67. package/dist/shared.d.ts +4 -14
  68. package/dist/shared.js +1 -43
  69. package/dist/stream/index.d.ts +2 -22
  70. package/dist/stream/index.js +1 -8
  71. package/dist/stream-CAO0x8_1.js +298 -0
  72. package/dist/stream-CAO0x8_1.js.map +1 -0
  73. package/dist/thread-list/index.d.ts +3 -47
  74. package/dist/thread-list/index.js +3 -18
  75. package/dist/thread-list-xJbGXFrE.js +228 -0
  76. package/dist/thread-list-xJbGXFrE.js.map +1 -0
  77. package/dist/ticket-CoJHBpdF.d.ts +32 -0
  78. package/dist/ticket-CoJHBpdF.d.ts.map +1 -0
  79. package/dist/value-BGGrx72L.js +13 -0
  80. package/dist/value-BGGrx72L.js.map +1 -0
  81. package/package.json +22 -22
  82. package/dist/attachment/index.js.map +0 -1
  83. package/dist/attachment-mbjsroli.d.ts +0 -9
  84. package/dist/chat/index.js.map +0 -1
  85. package/dist/chat-PSj1Fo4W.d.ts +0 -88
  86. package/dist/chunk-22EZJUPM.js +0 -120
  87. package/dist/chunk-22EZJUPM.js.map +0 -1
  88. package/dist/chunk-5FZKTDZR.js +0 -201
  89. package/dist/chunk-5FZKTDZR.js.map +0 -1
  90. package/dist/chunk-6XOJFLX4.js +0 -22
  91. package/dist/chunk-6XOJFLX4.js.map +0 -1
  92. package/dist/chunk-CKA4CE26.js +0 -25
  93. package/dist/chunk-CKA4CE26.js.map +0 -1
  94. package/dist/chunk-DLBNFBXQ.js +0 -425
  95. package/dist/chunk-DLBNFBXQ.js.map +0 -1
  96. package/dist/chunk-E73S3SOF.js +0 -404
  97. package/dist/chunk-E73S3SOF.js.map +0 -1
  98. package/dist/chunk-F6AKBUO3.js +0 -380
  99. package/dist/chunk-F6AKBUO3.js.map +0 -1
  100. package/dist/chunk-HGOPGZNQ.js +0 -195
  101. package/dist/chunk-HGOPGZNQ.js.map +0 -1
  102. package/dist/chunk-KHB5CHAU.js +0 -220
  103. package/dist/chunk-KHB5CHAU.js.map +0 -1
  104. package/dist/chunk-LHABOAOP.js +0 -316
  105. package/dist/chunk-LHABOAOP.js.map +0 -1
  106. package/dist/chunk-N5XQU36W.js +0 -410
  107. package/dist/chunk-N5XQU36W.js.map +0 -1
  108. package/dist/chunk-T3ZFZ3NP.js +0 -30
  109. package/dist/chunk-T3ZFZ3NP.js.map +0 -1
  110. package/dist/chunk-T4IYUEPH.js +0 -1612
  111. package/dist/chunk-T4IYUEPH.js.map +0 -1
  112. package/dist/chunk-W2AHKMRS.js +0 -1092
  113. package/dist/chunk-W2AHKMRS.js.map +0 -1
  114. package/dist/completion/index.js.map +0 -1
  115. package/dist/completion-input-DHlwrhxB.d.ts +0 -24
  116. package/dist/composer-6iCSIq6L.d.ts +0 -111
  117. package/dist/human-input/index.js.map +0 -1
  118. package/dist/human-input-CkLrZ8IC.d.ts +0 -27
  119. package/dist/image/index.js.map +0 -1
  120. package/dist/image-VZv6Ddsn.d.ts +0 -14
  121. package/dist/message/index.js.map +0 -1
  122. package/dist/message-CNRnZQwU.d.ts +0 -14
  123. package/dist/render-CRKWam2v.d.ts +0 -8
  124. package/dist/selection-toolbar/index.js.map +0 -1
  125. package/dist/selection-toolbar-Cwjbuv5S.d.ts +0 -14
  126. package/dist/shared.js.map +0 -1
  127. package/dist/stream/index.js.map +0 -1
  128. package/dist/thread-Bz6-1zaJ.d.ts +0 -12
  129. package/dist/thread-list/index.js.map +0 -1
  130. package/dist/thread-list-On8db4XL.d.ts +0 -34
  131. package/dist/ticket-pexI1zIa.d.ts +0 -33
@@ -1,356 +1,358 @@
1
- import {
2
- DefaultStatus,
3
- RelayConnection,
4
- resolveTicket,
5
- ticketSubprotocolPrefix
6
- } from "../chunk-5FZKTDZR.js";
7
-
8
- // src/sandbox-desktop/desktop.tsx
9
- import {
10
- forwardRef,
11
- useEffect,
12
- useImperativeHandle,
13
- useRef,
14
- useState
15
- } from "react";
16
-
1
+ import { i as DefaultStatus, n as ticketSubprotocolPrefix, r as resolveTicket, t as RelayConnection } from "../connection-DeNq8wHS.js";
2
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
17
4
  // src/sandbox-desktop/vnc-channel.ts
18
- var OPEN = 1;
19
- var CLOSED = 3;
5
+ const OPEN = 1;
6
+ const CLOSED = 3;
7
+ /**
8
+ * A WebSocket-shaped channel for noVNC to attach to. The hub's desktop relay mixes binary VNC frames
9
+ * with JSON control frames on one socket, and noVNC understands only the former, so the link
10
+ * hands this channel just the binary ones.
11
+ *
12
+ * The VNC server speaks first, so frames can arrive before noVNC has attached. They wait here
13
+ * until it installs its `onmessage`.
14
+ *
15
+ * noVNC checks a channel for these property names (`send`, `close`, `binaryType`, `onerror`,
16
+ * `onmessage`, `onopen`, `protocol`, `readyState`), as it does for a WebSocket or an
17
+ * RTCDataChannel, so they must all be present.
18
+ */
20
19
  var VncChannel = class {
21
- constructor(link, protocol = "") {
22
- this.link = link;
23
- this.protocol = protocol;
24
- }
25
- link;
26
- binaryType = "arraybuffer";
27
- protocol;
28
- onerror = null;
29
- onopen = null;
30
- onclose = null;
31
- handler = null;
32
- backlog = [];
33
- state = OPEN;
34
- get readyState() {
35
- return this.state;
36
- }
37
- get onmessage() {
38
- return this.handler;
39
- }
40
- set onmessage(handler) {
41
- this.handler = handler;
42
- if (handler === null) return;
43
- for (const data of this.backlog.splice(0)) handler({ data });
44
- }
45
- send(data) {
46
- if (this.state === OPEN) this.link.send(data);
47
- }
48
- /** noVNC is done with the connection. */
49
- close() {
50
- if (this.state !== OPEN) return;
51
- this.state = CLOSED;
52
- this.link.close();
53
- }
54
- /** A VNC frame arrived from the relay. */
55
- receive(data) {
56
- if (this.handler === null) this.backlog.push(data);
57
- else this.handler({ data });
58
- }
59
- /** The relay closed the socket. */
60
- closed(event) {
61
- if (this.state === CLOSED) return;
62
- this.state = CLOSED;
63
- this.backlog = [];
64
- this.onclose?.(event);
65
- }
20
+ link;
21
+ binaryType = "arraybuffer";
22
+ protocol;
23
+ onerror = null;
24
+ onopen = null;
25
+ onclose = null;
26
+ handler = null;
27
+ backlog = [];
28
+ state = OPEN;
29
+ constructor(link, protocol = "") {
30
+ this.link = link;
31
+ this.protocol = protocol;
32
+ }
33
+ get readyState() {
34
+ return this.state;
35
+ }
36
+ get onmessage() {
37
+ return this.handler;
38
+ }
39
+ set onmessage(handler) {
40
+ this.handler = handler;
41
+ if (handler === null) return;
42
+ for (const data of this.backlog.splice(0)) handler({ data });
43
+ }
44
+ send(data) {
45
+ if (this.state === OPEN) this.link.send(data);
46
+ }
47
+ /** noVNC is done with the connection. */
48
+ close() {
49
+ if (this.state !== OPEN) return;
50
+ this.state = CLOSED;
51
+ this.link.close();
52
+ }
53
+ /** A VNC frame arrived from the relay. */
54
+ receive(data) {
55
+ if (this.handler === null) this.backlog.push(data);
56
+ else this.handler({ data });
57
+ }
58
+ /** The relay closed the socket. */
59
+ closed(event) {
60
+ if (this.state === CLOSED) return;
61
+ this.state = CLOSED;
62
+ this.backlog = [];
63
+ this.onclose?.(event);
64
+ }
66
65
  };
67
-
68
66
  // src/sandbox-desktop/connection.ts
69
- var desktopSubprotocol = "anvia.desktop.v1";
70
- var retryableCodes = /* @__PURE__ */ new Set(["sandbox_unavailable", "timeout"]);
71
- var closeReason = /^([a-z_]+): (.*)$/s;
72
- var policyViolation = 1008;
67
+ /** Subprotocol the hub's `handleDesktopUpgrade` selects. */
68
+ const desktopSubprotocol = "anvia.desktop.v1";
69
+ /** Errors worth another attempt: the sandbox may simply not be reachable yet. */
70
+ const retryableCodes = /* @__PURE__ */ new Set(["sandbox_unavailable", "timeout"]);
71
+ /** The hub closes a socket it could not set up with `<error code>: <message>`. */
72
+ const closeReason = /^([a-z_]+): (.*)$/s;
73
+ /** The relay closes with this code when the viewer sent something it could not account for. */
74
+ const policyViolation = 1008;
75
+ /**
76
+ * One browser link to the hub's desktop relay, over a {@link RelayConnection}. It hands each
77
+ * accepted attachment to the owner as a channel for a VNC client, and speaks the relay's small
78
+ * JSON control protocol (`takeover`, `release`, and the `control` updates that come back).
79
+ */
73
80
  var DesktopConnection = class {
74
- relay;
75
- constructor(options) {
76
- let channel;
77
- let accepted = false;
78
- const protocol = {
79
- subprotocol: desktopSubprotocol,
80
- label: "desktop",
81
- open: (socket) => {
82
- accepted = false;
83
- channel = new VncChannel(
84
- { send: (data) => socket.send(data), close: () => socket.close(1e3) },
85
- socket.protocol
86
- );
87
- },
88
- message: (event, session) => {
89
- if (!accepted && channel !== void 0) {
90
- accepted = true;
91
- options.onChannel(channel, session.ready());
92
- }
93
- if (typeof event.data !== "string") {
94
- channel?.receive(event.data);
95
- return;
96
- }
97
- const message = parseText(event.data);
98
- if (message?.type === "control") {
99
- const { type: _type, ...control } = message;
100
- options.onControl(control);
101
- } else if (message?.type === "error") {
102
- options.onNotice?.({
103
- code: message.code ?? "protocol",
104
- message: message.message ?? "The request failed."
105
- });
106
- }
107
- },
108
- close: (event, session) => {
109
- channel?.closed(event);
110
- const match = closeReason.exec(event.reason);
111
- if (match !== null && !retryableCodes.has(match[1] ?? "")) {
112
- session.fatal(match[2] ?? "The desktop could not be opened.");
113
- } else if (event.code === policyViolation) {
114
- session.fatal(event.reason || "The desktop relay refused what the viewer sent.");
115
- }
116
- }
117
- };
118
- this.relay = new RelayConnection({
119
- url: options.url,
120
- getTicket: options.getTicket,
121
- onStatus: options.onStatus,
122
- reconnect: options.reconnect,
123
- protocol
124
- });
125
- }
126
- start() {
127
- this.relay.start();
128
- }
129
- retry() {
130
- this.relay.retry();
131
- }
132
- /** Ask for control, which pauses the agent's inputs. The answer arrives as a `control` update. */
133
- takeOver() {
134
- this.relay.sendJson({ type: "takeover" });
135
- }
136
- release() {
137
- this.relay.sendJson({ type: "release" });
138
- }
139
- dispose() {
140
- this.relay.dispose();
141
- }
81
+ relay;
82
+ constructor(options) {
83
+ let channel;
84
+ let accepted = false;
85
+ const protocol = {
86
+ subprotocol: desktopSubprotocol,
87
+ label: "desktop",
88
+ open: (socket) => {
89
+ accepted = false;
90
+ channel = new VncChannel({
91
+ send: (data) => socket.send(data),
92
+ close: () => socket.close(1e3)
93
+ }, socket.protocol);
94
+ },
95
+ message: (event, session) => {
96
+ if (!accepted && channel !== void 0) {
97
+ accepted = true;
98
+ options.onChannel(channel, session.ready());
99
+ }
100
+ if (typeof event.data !== "string") {
101
+ channel?.receive(event.data);
102
+ return;
103
+ }
104
+ const message = parseText(event.data);
105
+ if (message?.type === "control") {
106
+ const { type: _type, ...control } = message;
107
+ options.onControl(control);
108
+ } else if (message?.type === "error") options.onNotice?.({
109
+ code: message.code ?? "protocol",
110
+ message: message.message ?? "The request failed."
111
+ });
112
+ },
113
+ close: (event, session) => {
114
+ channel?.closed(event);
115
+ const match = closeReason.exec(event.reason);
116
+ if (match !== null && !retryableCodes.has(match[1] ?? "")) session.fatal(match[2] ?? "The desktop could not be opened.");
117
+ else if (event.code === policyViolation) session.fatal(event.reason || "The desktop relay refused what the viewer sent.");
118
+ }
119
+ };
120
+ this.relay = new RelayConnection({
121
+ url: options.url,
122
+ getTicket: options.getTicket,
123
+ onStatus: options.onStatus,
124
+ reconnect: options.reconnect,
125
+ protocol
126
+ });
127
+ }
128
+ start() {
129
+ this.relay.start();
130
+ }
131
+ retry() {
132
+ this.relay.retry();
133
+ }
134
+ /** Ask for control, which pauses the agent's inputs. The answer arrives as a `control` update. */
135
+ takeOver() {
136
+ this.relay.sendJson({ type: "takeover" });
137
+ }
138
+ release() {
139
+ this.relay.sendJson({ type: "release" });
140
+ }
141
+ dispose() {
142
+ this.relay.dispose();
143
+ }
142
144
  };
143
145
  function parseText(text) {
144
- try {
145
- const value = JSON.parse(text);
146
- if (typeof value !== "object" || value === null) return void 0;
147
- const type = value.type;
148
- return type === "control" || type === "error" ? value : void 0;
149
- } catch {
150
- return void 0;
151
- }
146
+ try {
147
+ const value = JSON.parse(text);
148
+ if (typeof value !== "object" || value === null) return void 0;
149
+ const type = value.type;
150
+ return type === "control" || type === "error" ? value : void 0;
151
+ } catch {
152
+ return;
153
+ }
152
154
  }
153
-
154
155
  // src/sandbox-desktop/desktop.tsx
155
- import { jsx, jsxs } from "react/jsx-runtime";
156
- var defaultQualityLevel = 6;
157
- var defaultCompressionLevel = 2;
156
+ const defaultQualityLevel = 6;
157
+ const defaultCompressionLevel = 2;
158
+ /** A level noVNC accepts: a whole number from 0 to 9. Anything else falls back to the default. */
158
159
  function level(value, fallback) {
159
- if (value === void 0 || !Number.isFinite(value)) return fallback;
160
- return Math.min(9, Math.max(0, Math.round(value)));
160
+ if (value === void 0 || !Number.isFinite(value)) return fallback;
161
+ return Math.min(9, Math.max(0, Math.round(value)));
161
162
  }
162
- var rootStyle = { position: "relative", width: "100%", height: "100%" };
163
- var screenStyle = { width: "100%", height: "100%" };
164
- var SandboxDesktop = forwardRef(
165
- function SandboxDesktop2({
166
- url,
167
- ticket,
168
- readOnly = false,
169
- scaleToFit = true,
170
- qualityLevel,
171
- compressionLevel,
172
- reconnect,
173
- onStatusChange,
174
- onControlChange,
175
- renderStatus,
176
- renderControls,
177
- style,
178
- ...rest
179
- }, ref) {
180
- const screen = useRef(null);
181
- const connection = useRef(void 0);
182
- const viewer = useRef(void 0);
183
- const [status, setStatus] = useState({ state: "loading" });
184
- const [control, setControl] = useState(void 0);
185
- const [notice, setNotice] = useState(void 0);
186
- const viewOnly = readOnly || control?.readOnly === true || control?.you !== true;
187
- const latest = useRef({
188
- ticket,
189
- reconnect,
190
- onStatusChange,
191
- onControlChange,
192
- scaleToFit,
193
- viewOnly,
194
- quality: level(qualityLevel, defaultQualityLevel),
195
- compression: level(compressionLevel, defaultCompressionLevel)
196
- });
197
- latest.current = {
198
- ticket,
199
- reconnect,
200
- onStatusChange,
201
- onControlChange,
202
- scaleToFit,
203
- viewOnly,
204
- quality: level(qualityLevel, defaultQualityLevel),
205
- compression: level(compressionLevel, defaultCompressionLevel)
206
- };
207
- useImperativeHandle(
208
- ref,
209
- () => ({
210
- focus: () => viewer.current?.focus(),
211
- reconnect: () => connection.current?.retry(),
212
- takeOver: () => connection.current?.takeOver(),
213
- release: () => connection.current?.release()
214
- }),
215
- []
216
- );
217
- useEffect(() => {
218
- if (viewer.current !== void 0) viewer.current.viewOnly = viewOnly;
219
- }, [viewOnly]);
220
- useEffect(() => {
221
- if (viewer.current !== void 0) viewer.current.scaleViewport = scaleToFit;
222
- }, [scaleToFit]);
223
- useEffect(() => {
224
- if (viewer.current !== void 0) viewer.current.qualityLevel = latest.current.quality;
225
- }, [qualityLevel]);
226
- useEffect(() => {
227
- if (viewer.current !== void 0) {
228
- viewer.current.compressionLevel = latest.current.compression;
229
- }
230
- }, [compressionLevel]);
231
- useEffect(() => {
232
- const container = screen.current;
233
- if (container === null) return void 0;
234
- let disposed = false;
235
- let teardown;
236
- const publish = (next) => {
237
- if (disposed) return;
238
- setStatus(next);
239
- if (next.state !== "open") setControl(void 0);
240
- latest.current.onStatusChange?.(next);
241
- };
242
- publish({ state: "loading" });
243
- void (async () => {
244
- const { default: Rfb } = await import("@novnc/novnc");
245
- if (disposed) return;
246
- const link = new DesktopConnection({
247
- url,
248
- getTicket: resolveTicket(() => latest.current.ticket),
249
- reconnect: latest.current.reconnect,
250
- onStatus: publish,
251
- onChannel: (channel) => {
252
- viewer.current?.disconnect();
253
- const rfb = new Rfb(container, channel);
254
- rfb.scaleViewport = latest.current.scaleToFit;
255
- rfb.clipViewport = false;
256
- rfb.resizeSession = false;
257
- rfb.viewOnly = latest.current.viewOnly;
258
- rfb.qualityLevel = latest.current.quality;
259
- rfb.compressionLevel = latest.current.compression;
260
- viewer.current = rfb;
261
- },
262
- onControl: (next) => {
263
- if (disposed) return;
264
- setControl(next);
265
- setNotice(void 0);
266
- latest.current.onControlChange?.(next);
267
- },
268
- onNotice: (next) => {
269
- if (!disposed) setNotice(next);
270
- }
271
- });
272
- connection.current = link;
273
- link.start();
274
- teardown = () => {
275
- link.dispose();
276
- viewer.current?.disconnect();
277
- viewer.current = void 0;
278
- connection.current = void 0;
279
- };
280
- })().catch((error) => {
281
- publish({
282
- state: "failed",
283
- message: error instanceof Error ? error.message : "Could not load the desktop viewer."
284
- });
285
- });
286
- return () => {
287
- disposed = true;
288
- teardown?.();
289
- };
290
- }, [url]);
291
- const retry = () => connection.current?.retry();
292
- const takeOver = () => connection.current?.takeOver();
293
- const release = () => connection.current?.release();
294
- const statusNode = renderStatus === void 0 ? /* @__PURE__ */ jsx(DefaultStatus, { status, retry }) : renderStatus(status, retry);
295
- const controlsState = {
296
- status,
297
- control,
298
- readOnly: readOnly || control?.readOnly === true,
299
- notice,
300
- takeOver,
301
- release
302
- };
303
- const controlsNode = renderControls === void 0 ? /* @__PURE__ */ jsx(DefaultControls, { ...controlsState }) : renderControls(controlsState);
304
- return /* @__PURE__ */ jsxs(
305
- "div",
306
- {
307
- ...rest,
308
- "data-state": status.state,
309
- "data-readonly": viewOnly ? "" : void 0,
310
- "data-control": control?.you === true ? "you" : control?.held === true ? "other" : "none",
311
- style: { ...rootStyle, ...style },
312
- children: [
313
- /* @__PURE__ */ jsx("div", { ref: screen, style: screenStyle }),
314
- controlsNode,
315
- statusNode
316
- ]
317
- }
318
- );
319
- }
320
- );
321
- var controlsStyle = {
322
- position: "absolute",
323
- top: 8,
324
- left: 8,
325
- zIndex: 1,
326
- display: "flex",
327
- gap: 8,
328
- alignItems: "center",
329
- padding: "2px 8px",
330
- borderRadius: 4,
331
- fontSize: 12,
332
- background: "rgba(0, 0, 0, 0.7)",
333
- color: "#fff"
163
+ const rootStyle = {
164
+ position: "relative",
165
+ width: "100%",
166
+ height: "100%"
334
167
  };
335
- function DefaultControls({
336
- status,
337
- control,
338
- readOnly,
339
- notice,
340
- takeOver,
341
- release
342
- }) {
343
- if (status.state !== "open" || control === void 0) return null;
344
- const who = control.you ? "You have control" : control.held ? `${control.holder ?? "Someone"} has control` : readOnly ? "View only" : "The agent has control";
345
- return /* @__PURE__ */ jsxs("div", { "data-sandbox-desktop-controls": "", style: controlsStyle, children: [
346
- /* @__PURE__ */ jsx("span", { children: who }),
347
- control.you ? /* @__PURE__ */ jsx("button", { type: "button", onClick: release, children: "Release" }) : !readOnly && !control.held ? /* @__PURE__ */ jsx("button", { type: "button", onClick: takeOver, children: "Take over" }) : null,
348
- notice === void 0 ? null : /* @__PURE__ */ jsx("span", { role: "alert", children: notice.message })
349
- ] });
350
- }
351
- export {
352
- SandboxDesktop,
353
- desktopSubprotocol,
354
- ticketSubprotocolPrefix
168
+ const screenStyle = {
169
+ width: "100%",
170
+ height: "100%"
171
+ };
172
+ /**
173
+ * A viewer for a remote sandbox's desktop, drawn with noVNC. noVNC is loaded on demand, so it costs
174
+ * nothing until a desktop mounts. It connects to the hub's desktop relay, reconnects with a fresh
175
+ * ticket when the link drops, and lets a person take over control from the agent.
176
+ */
177
+ const SandboxDesktop = forwardRef(function SandboxDesktop({ url, ticket, readOnly = false, scaleToFit = true, qualityLevel, compressionLevel, reconnect, onStatusChange, onControlChange, renderStatus, renderControls, style, ...rest }, ref) {
178
+ const screen = useRef(null);
179
+ const connection = useRef(void 0);
180
+ const viewer = useRef(void 0);
181
+ const [status, setStatus] = useState({ state: "loading" });
182
+ const [control, setControl] = useState(void 0);
183
+ const [notice, setNotice] = useState(void 0);
184
+ const viewOnly = readOnly || control?.readOnly === true || control?.you !== true;
185
+ const latest = useRef({
186
+ ticket,
187
+ reconnect,
188
+ onStatusChange,
189
+ onControlChange,
190
+ scaleToFit,
191
+ viewOnly,
192
+ quality: level(qualityLevel, defaultQualityLevel),
193
+ compression: level(compressionLevel, defaultCompressionLevel)
194
+ });
195
+ latest.current = {
196
+ ticket,
197
+ reconnect,
198
+ onStatusChange,
199
+ onControlChange,
200
+ scaleToFit,
201
+ viewOnly,
202
+ quality: level(qualityLevel, defaultQualityLevel),
203
+ compression: level(compressionLevel, defaultCompressionLevel)
204
+ };
205
+ useImperativeHandle(ref, () => ({
206
+ focus: () => viewer.current?.focus(),
207
+ reconnect: () => connection.current?.retry(),
208
+ takeOver: () => connection.current?.takeOver(),
209
+ release: () => connection.current?.release()
210
+ }), []);
211
+ useEffect(() => {
212
+ if (viewer.current !== void 0) viewer.current.viewOnly = viewOnly;
213
+ }, [viewOnly]);
214
+ useEffect(() => {
215
+ if (viewer.current !== void 0) viewer.current.scaleViewport = scaleToFit;
216
+ }, [scaleToFit]);
217
+ useEffect(() => {
218
+ if (viewer.current !== void 0) viewer.current.qualityLevel = latest.current.quality;
219
+ }, [qualityLevel]);
220
+ useEffect(() => {
221
+ if (viewer.current !== void 0) viewer.current.compressionLevel = latest.current.compression;
222
+ }, [compressionLevel]);
223
+ useEffect(() => {
224
+ const container = screen.current;
225
+ if (container === null) return void 0;
226
+ let disposed = false;
227
+ let teardown;
228
+ const publish = (next) => {
229
+ if (disposed) return;
230
+ setStatus(next);
231
+ if (next.state !== "open") setControl(void 0);
232
+ latest.current.onStatusChange?.(next);
233
+ };
234
+ publish({ state: "loading" });
235
+ (async () => {
236
+ const { default: Rfb } = await import("@novnc/novnc");
237
+ if (disposed) return;
238
+ const link = new DesktopConnection({
239
+ url,
240
+ getTicket: resolveTicket(() => latest.current.ticket),
241
+ reconnect: latest.current.reconnect,
242
+ onStatus: publish,
243
+ onChannel: (channel) => {
244
+ viewer.current?.disconnect();
245
+ const rfb = new Rfb(container, channel);
246
+ rfb.scaleViewport = latest.current.scaleToFit;
247
+ rfb.clipViewport = false;
248
+ rfb.resizeSession = false;
249
+ rfb.viewOnly = latest.current.viewOnly;
250
+ rfb.qualityLevel = latest.current.quality;
251
+ rfb.compressionLevel = latest.current.compression;
252
+ viewer.current = rfb;
253
+ },
254
+ onControl: (next) => {
255
+ if (disposed) return;
256
+ setControl(next);
257
+ setNotice(void 0);
258
+ latest.current.onControlChange?.(next);
259
+ },
260
+ onNotice: (next) => {
261
+ if (!disposed) setNotice(next);
262
+ }
263
+ });
264
+ connection.current = link;
265
+ link.start();
266
+ teardown = () => {
267
+ link.dispose();
268
+ viewer.current?.disconnect();
269
+ viewer.current = void 0;
270
+ connection.current = void 0;
271
+ };
272
+ })().catch((error) => {
273
+ publish({
274
+ state: "failed",
275
+ message: error instanceof Error ? error.message : "Could not load the desktop viewer."
276
+ });
277
+ });
278
+ return () => {
279
+ disposed = true;
280
+ teardown?.();
281
+ };
282
+ }, [url]);
283
+ const retry = () => connection.current?.retry();
284
+ const takeOver = () => connection.current?.takeOver();
285
+ const release = () => connection.current?.release();
286
+ const statusNode = renderStatus === void 0 ? /* @__PURE__ */ jsx(DefaultStatus, {
287
+ status,
288
+ retry
289
+ }) : renderStatus(status, retry);
290
+ const controlsState = {
291
+ status,
292
+ control,
293
+ readOnly: readOnly || control?.readOnly === true,
294
+ notice,
295
+ takeOver,
296
+ release
297
+ };
298
+ const controlsNode = renderControls === void 0 ? /* @__PURE__ */ jsx(DefaultControls, { ...controlsState }) : renderControls(controlsState);
299
+ return /* @__PURE__ */ jsxs("div", {
300
+ ...rest,
301
+ "data-state": status.state,
302
+ "data-readonly": viewOnly ? "" : void 0,
303
+ "data-control": control?.you === true ? "you" : control?.held === true ? "other" : "none",
304
+ style: {
305
+ ...rootStyle,
306
+ ...style
307
+ },
308
+ children: [
309
+ /* @__PURE__ */ jsx("div", {
310
+ ref: screen,
311
+ style: screenStyle
312
+ }),
313
+ controlsNode,
314
+ statusNode
315
+ ]
316
+ });
317
+ });
318
+ const controlsStyle = {
319
+ position: "absolute",
320
+ top: 8,
321
+ left: 8,
322
+ zIndex: 1,
323
+ display: "flex",
324
+ gap: 8,
325
+ alignItems: "center",
326
+ padding: "2px 8px",
327
+ borderRadius: 4,
328
+ fontSize: 12,
329
+ background: "rgba(0, 0, 0, 0.7)",
330
+ color: "#fff"
355
331
  };
332
+ function DefaultControls({ status, control, readOnly, notice, takeOver, release }) {
333
+ if (status.state !== "open" || control === void 0) return null;
334
+ const who = control.you ? "You have control" : control.held ? `${control.holder ?? "Someone"} has control` : readOnly ? "View only" : "The agent has control";
335
+ return /* @__PURE__ */ jsxs("div", {
336
+ "data-sandbox-desktop-controls": "",
337
+ style: controlsStyle,
338
+ children: [
339
+ /* @__PURE__ */ jsx("span", { children: who }),
340
+ control.you ? /* @__PURE__ */ jsx("button", {
341
+ type: "button",
342
+ onClick: release,
343
+ children: "Release"
344
+ }) : !readOnly && !control.held ? /* @__PURE__ */ jsx("button", {
345
+ type: "button",
346
+ onClick: takeOver,
347
+ children: "Take over"
348
+ }) : null,
349
+ notice === void 0 ? null : /* @__PURE__ */ jsx("span", {
350
+ role: "alert",
351
+ children: notice.message
352
+ })
353
+ ]
354
+ });
355
+ }
356
+ export { SandboxDesktop, desktopSubprotocol, ticketSubprotocolPrefix };
357
+
356
358
  //# sourceMappingURL=index.js.map