tensorlake 0.4.42 → 0.4.45

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/dist/index.cjs CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  APIClient: () => APIClient,
34
34
  CloudClient: () => CloudClient,
35
35
  ContainerState: () => ContainerState,
36
+ Desktop: () => Desktop,
36
37
  Image: () => Image,
37
38
  ImageBuildOperationType: () => ImageBuildOperationType,
38
39
  OutputMode: () => OutputMode,
@@ -53,6 +54,7 @@ __export(index_exports, {
53
54
  SandboxStatus: () => SandboxStatus,
54
55
  SnapshotStatus: () => SnapshotStatus,
55
56
  StdinMode: () => StdinMode,
57
+ TcpTunnel: () => TcpTunnel,
56
58
  createSandboxImage: () => createSandboxImage,
57
59
  dockerfileContent: () => dockerfileContent
58
60
  });
@@ -431,6 +433,2395 @@ function fromSnakeKeys(obj, idField) {
431
433
  return obj;
432
434
  }
433
435
 
436
+ // src/desktop.ts
437
+ var import_node_zlib = require("zlib");
438
+
439
+ // src/tunnel.ts
440
+ var net = __toESM(require("net"), 1);
441
+ var import_ws = __toESM(require("ws"), 1);
442
+ var DEFAULT_TUNNEL_CONNECT_TIMEOUT_MS = 1e4;
443
+ var WEBSOCKET_KEEPALIVE_INTERVAL_MS = 15e3;
444
+ async function openTunnelWebSocket(options) {
445
+ const wsUrl = buildTunnelWsUrl(options.baseUrl, options.remotePort);
446
+ const timeoutMs = options.connectTimeoutMs ?? DEFAULT_TUNNEL_CONNECT_TIMEOUT_MS;
447
+ return new Promise((resolve, reject) => {
448
+ const socket = new import_ws.default(wsUrl, {
449
+ headers: options.wsHeaders
450
+ });
451
+ let settled = false;
452
+ const timer = setTimeout(() => {
453
+ fail(
454
+ new SandboxError(
455
+ `timed out while connecting tunnel websocket after ${(timeoutMs / 1e3).toFixed(2)}s`
456
+ )
457
+ );
458
+ }, timeoutMs);
459
+ const cleanup = () => {
460
+ clearTimeout(timer);
461
+ socket.removeListener("open", onOpen);
462
+ socket.removeListener("error", onError);
463
+ socket.removeListener("close", onCloseBeforeOpen);
464
+ socket.removeListener("unexpected-response", onUnexpectedResponse);
465
+ };
466
+ const settle = (callback) => {
467
+ if (settled) return;
468
+ settled = true;
469
+ cleanup();
470
+ callback();
471
+ };
472
+ const fail = (error) => {
473
+ settle(() => {
474
+ reject(error);
475
+ });
476
+ };
477
+ const onOpen = () => {
478
+ settle(() => resolve(socket));
479
+ };
480
+ const onError = (error) => {
481
+ fail(new SandboxConnectionError(error.message));
482
+ };
483
+ const onCloseBeforeOpen = (code, reason) => {
484
+ const closeReason = reason.length > 0 ? reason.toString("utf8") : "no reason";
485
+ fail(
486
+ new SandboxError(
487
+ `tunnel websocket closed before opening: ${code} ${closeReason}`
488
+ )
489
+ );
490
+ };
491
+ const onUnexpectedResponse = (_request, response) => {
492
+ const status = response.statusCode ?? 0;
493
+ const statusMessage = response.statusMessage?.trim();
494
+ fail(
495
+ new SandboxError(
496
+ `tunnel websocket handshake failed with HTTP ${status}${statusMessage ? ` ${statusMessage}` : ""}`
497
+ )
498
+ );
499
+ };
500
+ socket.on("open", onOpen);
501
+ socket.on("error", onError);
502
+ socket.on("close", onCloseBeforeOpen);
503
+ socket.on("unexpected-response", onUnexpectedResponse);
504
+ });
505
+ }
506
+ var TunnelByteStream = class _TunnelByteStream {
507
+ socket;
508
+ readBuffer = Buffer.alloc(0);
509
+ pendingReads = [];
510
+ closeError = null;
511
+ closePromise = null;
512
+ keepAliveInterval;
513
+ constructor(socket) {
514
+ this.socket = socket;
515
+ this.keepAliveInterval = setInterval(() => {
516
+ if (socket.readyState === import_ws.default.OPEN) {
517
+ socket.ping();
518
+ }
519
+ }, WEBSOCKET_KEEPALIVE_INTERVAL_MS);
520
+ this.keepAliveInterval.unref?.();
521
+ socket.on("message", (message, isBinary) => {
522
+ if (!isBinary) {
523
+ this.fail(
524
+ new SandboxError("desktop tunnel received unexpected text frame")
525
+ );
526
+ return;
527
+ }
528
+ this.pushBytes(normalizeWebSocketData(message));
529
+ });
530
+ socket.on("ping", (data) => {
531
+ if (socket.readyState === import_ws.default.OPEN) {
532
+ socket.pong(data, false, () => {
533
+ });
534
+ }
535
+ });
536
+ socket.on("close", (_code, reason) => {
537
+ clearInterval(this.keepAliveInterval);
538
+ const closeReason = reason.length > 0 ? reason.toString("utf8") : "desktop tunnel closed unexpectedly";
539
+ this.fail(new SandboxError(closeReason));
540
+ });
541
+ socket.on("error", (error) => {
542
+ clearInterval(this.keepAliveInterval);
543
+ this.fail(new SandboxConnectionError(error.message));
544
+ });
545
+ }
546
+ static async connect(options) {
547
+ const socket = await openTunnelWebSocket(options);
548
+ return new _TunnelByteStream(socket);
549
+ }
550
+ async readExactly(length) {
551
+ if (length < 0) {
552
+ throw new SandboxError(`read length must be >= 0, got ${length}`);
553
+ }
554
+ if (length === 0) {
555
+ return Buffer.alloc(0);
556
+ }
557
+ if (this.readBuffer.length >= length) {
558
+ const chunk = this.readBuffer.subarray(0, length);
559
+ this.readBuffer = this.readBuffer.subarray(length);
560
+ return chunk;
561
+ }
562
+ if (this.closeError) {
563
+ throw this.closeError;
564
+ }
565
+ return new Promise((resolve, reject) => {
566
+ this.pendingReads.push({ length, resolve, reject });
567
+ });
568
+ }
569
+ async writeAll(data) {
570
+ if (this.closeError) {
571
+ throw this.closeError;
572
+ }
573
+ if (this.socket.readyState !== import_ws.default.OPEN) {
574
+ throw new SandboxError("desktop tunnel is not connected");
575
+ }
576
+ const payload = Buffer.from(data);
577
+ await new Promise((resolve, reject) => {
578
+ this.socket.send(payload, { binary: true }, (error) => {
579
+ if (error) {
580
+ reject(new SandboxConnectionError(error.message));
581
+ return;
582
+ }
583
+ resolve();
584
+ });
585
+ });
586
+ }
587
+ async close() {
588
+ if (this.closePromise) {
589
+ return this.closePromise;
590
+ }
591
+ if (this.socket.readyState === import_ws.default.CLOSED || this.socket.readyState === import_ws.default.CLOSING) {
592
+ this.closePromise = Promise.resolve();
593
+ return this.closePromise;
594
+ }
595
+ this.closePromise = new Promise((resolve) => {
596
+ const onClose = () => {
597
+ this.socket.removeListener("close", onClose);
598
+ resolve();
599
+ };
600
+ this.socket.on("close", onClose);
601
+ this.socket.close();
602
+ });
603
+ return this.closePromise;
604
+ }
605
+ pushBytes(chunk) {
606
+ this.readBuffer = this.readBuffer.length === 0 ? chunk : Buffer.from(Buffer.concat([this.readBuffer, chunk]));
607
+ this.flushPendingReads();
608
+ }
609
+ flushPendingReads() {
610
+ while (this.pendingReads.length > 0) {
611
+ const next = this.pendingReads[0];
612
+ if (this.readBuffer.length < next.length) {
613
+ break;
614
+ }
615
+ const chunk = this.readBuffer.subarray(0, next.length);
616
+ this.readBuffer = this.readBuffer.subarray(next.length);
617
+ this.pendingReads.shift();
618
+ next.resolve(chunk);
619
+ }
620
+ }
621
+ fail(error) {
622
+ if (this.closeError) {
623
+ return;
624
+ }
625
+ this.closeError = error;
626
+ while (this.pendingReads.length > 0) {
627
+ const pending = this.pendingReads.shift();
628
+ pending?.reject(error);
629
+ }
630
+ }
631
+ };
632
+ var TcpTunnel = class _TcpTunnel {
633
+ remotePort;
634
+ localHost;
635
+ localPort;
636
+ baseUrl;
637
+ wsHeaders;
638
+ server;
639
+ connectTimeoutMs;
640
+ activeRelays = /* @__PURE__ */ new Set();
641
+ closePromise = null;
642
+ constructor(options) {
643
+ this.baseUrl = options.baseUrl;
644
+ this.wsHeaders = options.wsHeaders;
645
+ this.remotePort = options.remotePort;
646
+ this.localHost = options.localHost;
647
+ this.localPort = options.localPort;
648
+ this.server = options.server;
649
+ this.connectTimeoutMs = options.connectTimeoutMs;
650
+ }
651
+ static async listen(options) {
652
+ const remotePort = validatePort(options.remotePort, "remote port");
653
+ const localHost = options.localHost ?? "127.0.0.1";
654
+ const localPort = validatePort(
655
+ options.localPort ?? remotePort,
656
+ "local port",
657
+ true
658
+ );
659
+ const connectTimeoutMs = secondsToMillis(options.connectTimeout ?? 10);
660
+ const server = net.createServer();
661
+ await listenServer(server, localPort, localHost);
662
+ const address = server.address();
663
+ if (!address || typeof address === "string") {
664
+ server.close();
665
+ throw new SandboxError("failed to determine bound tunnel address");
666
+ }
667
+ const tunnel = new _TcpTunnel({
668
+ baseUrl: options.baseUrl,
669
+ wsHeaders: options.wsHeaders,
670
+ remotePort,
671
+ localHost,
672
+ localPort: address.port,
673
+ server,
674
+ connectTimeoutMs
675
+ });
676
+ server.on("connection", (localSocket) => {
677
+ void tunnel.handleConnection(localSocket);
678
+ });
679
+ return tunnel;
680
+ }
681
+ address() {
682
+ return { host: this.localHost, port: this.localPort };
683
+ }
684
+ async close() {
685
+ if (this.closePromise) {
686
+ return this.closePromise;
687
+ }
688
+ for (const relay of this.activeRelays) {
689
+ relay.localSocket.destroy();
690
+ relay.websocket?.close();
691
+ }
692
+ this.closePromise = new Promise((resolve, reject) => {
693
+ this.server.close((error) => {
694
+ if (error) {
695
+ reject(new SandboxError(`failed to close tunnel listener: ${error.message}`));
696
+ return;
697
+ }
698
+ resolve();
699
+ });
700
+ });
701
+ return this.closePromise;
702
+ }
703
+ async handleConnection(localSocket) {
704
+ localSocket.setNoDelay(true);
705
+ const relay = { localSocket, websocket: null };
706
+ this.activeRelays.add(relay);
707
+ try {
708
+ relay.websocket = await openTunnelWebSocket({
709
+ baseUrl: this.baseUrl,
710
+ wsHeaders: this.wsHeaders,
711
+ remotePort: this.remotePort,
712
+ connectTimeoutMs: this.connectTimeoutMs
713
+ });
714
+ await relaySocket(localSocket, relay.websocket);
715
+ } catch (error) {
716
+ localSocket.destroy(
717
+ error instanceof Error ? error : new Error(String(error))
718
+ );
719
+ } finally {
720
+ this.activeRelays.delete(relay);
721
+ }
722
+ }
723
+ };
724
+ function buildTunnelWsUrl(baseUrl, remotePort) {
725
+ const url = new URL(baseUrl);
726
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
727
+ url.pathname = "/api/v1/tunnels/tcp";
728
+ url.search = `port=${encodeURIComponent(String(remotePort))}`;
729
+ return url.toString();
730
+ }
731
+ function normalizeWebSocketData(message) {
732
+ if (Buffer.isBuffer(message)) return message;
733
+ if (Array.isArray(message)) {
734
+ return Buffer.concat(message.map((part) => Buffer.from(part)));
735
+ }
736
+ return Buffer.from(message);
737
+ }
738
+ async function relaySocket(localSocket, websocket) {
739
+ return new Promise((resolve) => {
740
+ let settled = false;
741
+ const keepAliveInterval = setInterval(() => {
742
+ if (websocket.readyState === import_ws.default.OPEN) {
743
+ websocket.ping();
744
+ }
745
+ }, WEBSOCKET_KEEPALIVE_INTERVAL_MS);
746
+ keepAliveInterval.unref?.();
747
+ const finish = () => {
748
+ if (settled) return;
749
+ settled = true;
750
+ clearInterval(keepAliveInterval);
751
+ cleanup();
752
+ resolve();
753
+ };
754
+ const cleanup = () => {
755
+ localSocket.removeListener("data", onLocalData);
756
+ localSocket.removeListener("end", onLocalEnd);
757
+ localSocket.removeListener("close", onLocalClose);
758
+ localSocket.removeListener("error", onLocalError);
759
+ websocket.removeListener("message", onWsMessage);
760
+ websocket.removeListener("close", onWsClose);
761
+ websocket.removeListener("error", onWsError);
762
+ websocket.removeListener("ping", onWsPing);
763
+ };
764
+ const onLocalData = (chunk) => {
765
+ if (websocket.readyState !== import_ws.default.OPEN) {
766
+ localSocket.destroy();
767
+ return;
768
+ }
769
+ websocket.send(chunk, { binary: true }, (error) => {
770
+ if (error) {
771
+ localSocket.destroy(error);
772
+ }
773
+ });
774
+ };
775
+ const onLocalEnd = () => {
776
+ if (websocket.readyState === import_ws.default.OPEN) {
777
+ websocket.close();
778
+ }
779
+ };
780
+ const onLocalClose = () => {
781
+ if (websocket.readyState === import_ws.default.OPEN || websocket.readyState === import_ws.default.CONNECTING) {
782
+ websocket.close();
783
+ }
784
+ finish();
785
+ };
786
+ const onLocalError = () => {
787
+ websocket.close();
788
+ finish();
789
+ };
790
+ const onWsMessage = (message, isBinary) => {
791
+ if (!isBinary) {
792
+ localSocket.destroy(
793
+ new SandboxError("received unexpected text frame from tunnel")
794
+ );
795
+ websocket.close();
796
+ return;
797
+ }
798
+ const payload = normalizeWebSocketData(message);
799
+ if (!localSocket.destroyed) {
800
+ localSocket.write(payload);
801
+ }
802
+ };
803
+ const onWsClose = () => {
804
+ if (!localSocket.destroyed) {
805
+ localSocket.end();
806
+ }
807
+ finish();
808
+ };
809
+ const onWsError = (error) => {
810
+ localSocket.destroy(error);
811
+ finish();
812
+ };
813
+ const onWsPing = (data) => {
814
+ if (websocket.readyState === import_ws.default.OPEN) {
815
+ websocket.pong(data, false, () => {
816
+ });
817
+ }
818
+ };
819
+ localSocket.on("data", onLocalData);
820
+ localSocket.on("end", onLocalEnd);
821
+ localSocket.on("close", onLocalClose);
822
+ localSocket.on("error", onLocalError);
823
+ websocket.on("message", onWsMessage);
824
+ websocket.on("close", onWsClose);
825
+ websocket.on("error", onWsError);
826
+ websocket.on("ping", onWsPing);
827
+ });
828
+ }
829
+ async function listenServer(server, localPort, localHost) {
830
+ await new Promise((resolve, reject) => {
831
+ const onError = (error) => {
832
+ server.removeListener("listening", onListening);
833
+ reject(
834
+ new SandboxError(
835
+ `failed to bind local tunnel listener on ${localHost}:${localPort}: ${error.message}`
836
+ )
837
+ );
838
+ };
839
+ const onListening = () => {
840
+ server.removeListener("error", onError);
841
+ resolve();
842
+ };
843
+ server.once("error", onError);
844
+ server.once("listening", onListening);
845
+ server.listen(localPort, localHost);
846
+ });
847
+ }
848
+ function validatePort(port, label, allowZero = false) {
849
+ if (!Number.isInteger(port)) {
850
+ throw new SandboxError(`${label} must be an integer, got ${port}`);
851
+ }
852
+ if (allowZero && port === 0) {
853
+ return port;
854
+ }
855
+ if (port < 1 || port > 65535) {
856
+ throw new SandboxError(`${label} must be between 1 and 65535, got ${port}`);
857
+ }
858
+ return port;
859
+ }
860
+ function secondsToMillis(seconds) {
861
+ if (!Number.isFinite(seconds) || seconds < 0) {
862
+ throw new SandboxError(`timeout must be >= 0 seconds, got ${seconds}`);
863
+ }
864
+ return Math.round(seconds * 1e3);
865
+ }
866
+ async function withTimeout(timeoutMs, operation, timeoutMessage) {
867
+ return new Promise((resolve, reject) => {
868
+ const timer = setTimeout(() => {
869
+ reject(new SandboxError(timeoutMessage));
870
+ }, timeoutMs);
871
+ void operation().then((value) => {
872
+ clearTimeout(timer);
873
+ resolve(value);
874
+ }).catch((error) => {
875
+ clearTimeout(timer);
876
+ reject(error);
877
+ });
878
+ });
879
+ }
880
+
881
+ // src/desktop.ts
882
+ var SECURITY_TYPE_NONE = 1;
883
+ var SECURITY_TYPE_VNC_AUTH = 2;
884
+ var ENCODING_RAW = 0;
885
+ var ENCODING_DESKTOP_SIZE = -223;
886
+ var BUTTON_LEFT_MASK = 1;
887
+ var BUTTON_MIDDLE_MASK = 1 << 1;
888
+ var BUTTON_RIGHT_MASK = 1 << 2;
889
+ var BUTTON_SCROLL_UP_MASK = 1 << 3;
890
+ var BUTTON_SCROLL_DOWN_MASK = 1 << 4;
891
+ var PNG_SIGNATURE = Buffer.from([
892
+ 137,
893
+ 80,
894
+ 78,
895
+ 71,
896
+ 13,
897
+ 10,
898
+ 26,
899
+ 10
900
+ ]);
901
+ var CRC32_TABLE = buildCrc32Table();
902
+ var DesktopSession = class _DesktopSession {
903
+ transport;
904
+ width;
905
+ height;
906
+ pixelFormat;
907
+ framebuffer;
908
+ pointerX = 0;
909
+ pointerY = 0;
910
+ buttonMask = 0;
911
+ framebufferVersion = 0;
912
+ closed = false;
913
+ updateLoopError = null;
914
+ updateSignal = createDeferredSignal();
915
+ updateLoopPromise = null;
916
+ constructor(options) {
917
+ this.transport = options.transport;
918
+ this.width = options.width;
919
+ this.height = options.height;
920
+ this.pixelFormat = options.pixelFormat;
921
+ this.framebuffer = options.framebuffer;
922
+ }
923
+ static async connect(transport, password, shared = true) {
924
+ const serverVersion = await ProtocolVersion.read(transport);
925
+ const clientVersion = serverVersion.negotiated();
926
+ await transport.writeAll(Buffer.from(clientVersion.render(), "ascii"));
927
+ await negotiateSecurity(transport, clientVersion, password);
928
+ await transport.writeAll(Uint8Array.of(shared ? 1 : 0));
929
+ const init = await ServerInit.read(transport);
930
+ if (!init.pixelFormat.trueColor) {
931
+ throw new SandboxError(
932
+ "desktop sessions require a true-color VNC pixel format"
933
+ );
934
+ }
935
+ const pixelFormat = PixelFormat.preferred();
936
+ await sendSetPixelFormat(transport, pixelFormat);
937
+ await sendSetEncodings(transport, [ENCODING_RAW, ENCODING_DESKTOP_SIZE]);
938
+ const session = new _DesktopSession({
939
+ transport,
940
+ width: init.width,
941
+ height: init.height,
942
+ pixelFormat,
943
+ framebuffer: allocateFramebuffer(init.width, init.height)
944
+ });
945
+ session.startFramebufferUpdates();
946
+ return session;
947
+ }
948
+ async close() {
949
+ this.closed = true;
950
+ this.updateSignal.resolve();
951
+ await this.transport.close();
952
+ await this.updateLoopPromise;
953
+ }
954
+ async screenshot(timeoutSeconds = 5) {
955
+ await this.waitForFramebufferVersion(
956
+ 1,
957
+ timeoutSeconds,
958
+ `timed out waiting for initial desktop framebuffer after ${timeoutSeconds.toFixed(2)}s`
959
+ );
960
+ return encodePng(this.width, this.height, this.framebuffer);
961
+ }
962
+ getFrameVersion() {
963
+ return this.framebufferVersion;
964
+ }
965
+ async screenshotAfter(frameVersion, timeoutSeconds = 1) {
966
+ const minimumVersion = validateNonNegativeInteger(frameVersion, "frame version") + 1;
967
+ if (this.framebufferVersion < minimumVersion) {
968
+ await this.waitForFramebufferVersion(
969
+ minimumVersion,
970
+ timeoutSeconds,
971
+ `timed out waiting for a fresher desktop framebuffer after ${timeoutSeconds.toFixed(2)}s`
972
+ );
973
+ }
974
+ return encodePng(this.width, this.height, this.framebuffer);
975
+ }
976
+ async moveMouse(x, y) {
977
+ const nextX = validateCoordinate(x, "mouse x coordinate");
978
+ const nextY = validateCoordinate(y, "mouse y coordinate");
979
+ this.ensurePointerInBounds(nextX, nextY);
980
+ this.pointerX = nextX;
981
+ this.pointerY = nextY;
982
+ await sendPointerEvent(this.transport, this.buttonMask, nextX, nextY);
983
+ }
984
+ async mousePress(options) {
985
+ const button = options?.button ?? "left";
986
+ await this.moveIfRequested(options?.x, options?.y);
987
+ this.buttonMask |= buttonMask(button);
988
+ await sendPointerEvent(
989
+ this.transport,
990
+ this.buttonMask,
991
+ this.pointerX,
992
+ this.pointerY
993
+ );
994
+ }
995
+ async mouseRelease(options) {
996
+ const button = options?.button ?? "left";
997
+ await this.moveIfRequested(options?.x, options?.y);
998
+ this.buttonMask &= ~buttonMask(button);
999
+ await sendPointerEvent(
1000
+ this.transport,
1001
+ this.buttonMask,
1002
+ this.pointerX,
1003
+ this.pointerY
1004
+ );
1005
+ }
1006
+ async click(options) {
1007
+ await this.mousePress(options);
1008
+ await this.mouseRelease({ button: options?.button ?? "left" });
1009
+ }
1010
+ async doubleClick(options) {
1011
+ const button = options?.button ?? "left";
1012
+ const delayMs = options?.delayMs ?? 50;
1013
+ validateNonNegativeInteger(delayMs, "double click delay");
1014
+ await this.click({ button, x: options?.x, y: options?.y });
1015
+ if (delayMs > 0) {
1016
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1017
+ }
1018
+ await this.click({ button });
1019
+ }
1020
+ async scroll(steps, x, y) {
1021
+ const normalizedSteps = validateInteger(steps, "scroll steps");
1022
+ await this.moveIfRequested(x, y);
1023
+ if (normalizedSteps === 0) {
1024
+ return;
1025
+ }
1026
+ const wheelMask = normalizedSteps > 0 ? BUTTON_SCROLL_UP_MASK : BUTTON_SCROLL_DOWN_MASK;
1027
+ const stepCount = Math.abs(normalizedSteps);
1028
+ for (let index = 0; index < stepCount; index += 1) {
1029
+ await sendPointerEvent(
1030
+ this.transport,
1031
+ this.buttonMask | wheelMask,
1032
+ this.pointerX,
1033
+ this.pointerY
1034
+ );
1035
+ await sendPointerEvent(
1036
+ this.transport,
1037
+ this.buttonMask,
1038
+ this.pointerX,
1039
+ this.pointerY
1040
+ );
1041
+ }
1042
+ }
1043
+ async keyDown(key) {
1044
+ await sendKeyEvent(this.transport, true, keysymFromKeyName(key));
1045
+ }
1046
+ async keyUp(key) {
1047
+ await sendKeyEvent(this.transport, false, keysymFromKeyName(key));
1048
+ }
1049
+ async press(keys) {
1050
+ const parts = Array.isArray(keys) ? keys : [keys];
1051
+ if (parts.length === 0) {
1052
+ throw new SandboxError("desktop press requires at least one key");
1053
+ }
1054
+ const keysyms = parts.map((part) => keysymFromKeyName(part));
1055
+ if (keysyms.length === 1) {
1056
+ await sendKeyEvent(this.transport, true, keysyms[0]);
1057
+ await sendKeyEvent(this.transport, false, keysyms[0]);
1058
+ return;
1059
+ }
1060
+ for (const keysym of keysyms.slice(0, -1)) {
1061
+ await sendKeyEvent(this.transport, true, keysym);
1062
+ }
1063
+ const last = keysyms[keysyms.length - 1];
1064
+ await sendKeyEvent(this.transport, true, last);
1065
+ await sendKeyEvent(this.transport, false, last);
1066
+ for (const keysym of keysyms.slice(0, -1).reverse()) {
1067
+ await sendKeyEvent(this.transport, false, keysym);
1068
+ }
1069
+ }
1070
+ async typeText(text) {
1071
+ for (const char of text) {
1072
+ const keysym = keysymFromChar(char);
1073
+ await sendKeyEvent(this.transport, true, keysym);
1074
+ await sendKeyEvent(this.transport, false, keysym);
1075
+ }
1076
+ }
1077
+ async moveIfRequested(x, y) {
1078
+ if (x == null && y == null) {
1079
+ return;
1080
+ }
1081
+ if (x == null || y == null) {
1082
+ throw new SandboxError(
1083
+ "desktop pointer actions require both x and y when specifying coordinates"
1084
+ );
1085
+ }
1086
+ await this.moveMouse(x, y);
1087
+ }
1088
+ ensurePointerInBounds(x, y) {
1089
+ if (this.width > 0 && x >= this.width) {
1090
+ throw new SandboxError(
1091
+ `mouse x coordinate ${x} is outside desktop width ${this.width}`
1092
+ );
1093
+ }
1094
+ if (this.height > 0 && y >= this.height) {
1095
+ throw new SandboxError(
1096
+ `mouse y coordinate ${y} is outside desktop height ${this.height}`
1097
+ );
1098
+ }
1099
+ }
1100
+ startFramebufferUpdates() {
1101
+ if (this.updateLoopPromise) {
1102
+ return;
1103
+ }
1104
+ this.updateLoopPromise = this.runFramebufferUpdateLoop().catch((error) => {
1105
+ if (this.closed) {
1106
+ return;
1107
+ }
1108
+ this.updateLoopError = normalizeError(error);
1109
+ this.updateSignal.resolve();
1110
+ });
1111
+ }
1112
+ async runFramebufferUpdateLoop() {
1113
+ let incremental = false;
1114
+ while (!this.closed) {
1115
+ await sendFramebufferUpdateRequest(
1116
+ this.transport,
1117
+ incremental,
1118
+ 0,
1119
+ 0,
1120
+ this.width,
1121
+ this.height
1122
+ );
1123
+ const outcome = await this.readUntilFramebufferUpdate();
1124
+ if (outcome.sawResize && !outcome.sawRaw) {
1125
+ incremental = false;
1126
+ continue;
1127
+ }
1128
+ if (outcome.sawRaw) {
1129
+ this.framebufferVersion += 1;
1130
+ this.updateSignal.resolve();
1131
+ }
1132
+ incremental = true;
1133
+ }
1134
+ }
1135
+ async readUntilFramebufferUpdate() {
1136
+ while (true) {
1137
+ const outcome = await this.readServerMessage();
1138
+ if (outcome.kind === "framebufferUpdate") {
1139
+ return outcome;
1140
+ }
1141
+ }
1142
+ }
1143
+ async waitForFramebufferVersion(minimumVersion, timeoutSeconds, timeoutMessage) {
1144
+ const timeoutMs = secondsToMillis2(timeoutSeconds);
1145
+ const deadline = Date.now() + timeoutMs;
1146
+ while (this.framebufferVersion < minimumVersion) {
1147
+ if (this.updateLoopError) {
1148
+ throw this.updateLoopError;
1149
+ }
1150
+ if (this.closed) {
1151
+ throw new SandboxError("desktop session is closed");
1152
+ }
1153
+ const observedVersion = this.framebufferVersion;
1154
+ const waitForUpdate = this.updateSignal.wait();
1155
+ if (this.framebufferVersion !== observedVersion) {
1156
+ continue;
1157
+ }
1158
+ const remainingMs = Math.max(0, deadline - Date.now());
1159
+ if (remainingMs === 0) {
1160
+ break;
1161
+ }
1162
+ await withTimeout(remainingMs, () => waitForUpdate, timeoutMessage);
1163
+ }
1164
+ if (this.framebufferVersion < minimumVersion) {
1165
+ throw new SandboxError(timeoutMessage);
1166
+ }
1167
+ }
1168
+ async readServerMessage() {
1169
+ const messageType = await readU8(this.transport);
1170
+ if (messageType === 0) {
1171
+ return this.readFramebufferUpdate();
1172
+ }
1173
+ if (messageType === 1) {
1174
+ await this.readSetColorMapEntries();
1175
+ return { kind: "bell" };
1176
+ }
1177
+ if (messageType === 2) {
1178
+ return { kind: "bell" };
1179
+ }
1180
+ if (messageType === 3) {
1181
+ await this.readServerCutText();
1182
+ return { kind: "serverCutText" };
1183
+ }
1184
+ throw new SandboxError(`unsupported VNC server message type ${messageType}`);
1185
+ }
1186
+ async readFramebufferUpdate() {
1187
+ await readU8(this.transport);
1188
+ const rectangleCount = await readU16(this.transport);
1189
+ let sawResize = false;
1190
+ let sawRaw = false;
1191
+ for (let index = 0; index < rectangleCount; index += 1) {
1192
+ const x = await readU16(this.transport);
1193
+ const y = await readU16(this.transport);
1194
+ const width = await readU16(this.transport);
1195
+ const height = await readU16(this.transport);
1196
+ const encoding = await readI32(this.transport);
1197
+ if (encoding === ENCODING_RAW) {
1198
+ const bytesPerPixel = this.pixelFormat.bytesPerPixel();
1199
+ const length = width * height * bytesPerPixel;
1200
+ const data = await this.transport.readExactly(length);
1201
+ this.blitRawRectangle(x, y, width, height, data);
1202
+ sawRaw = true;
1203
+ continue;
1204
+ }
1205
+ if (encoding === ENCODING_DESKTOP_SIZE) {
1206
+ this.resizeFramebuffer(width, height);
1207
+ sawResize = true;
1208
+ continue;
1209
+ }
1210
+ throw new SandboxError(`unsupported VNC rectangle encoding ${encoding}`);
1211
+ }
1212
+ return { kind: "framebufferUpdate", sawResize, sawRaw };
1213
+ }
1214
+ async readSetColorMapEntries() {
1215
+ await readU8(this.transport);
1216
+ await readU16(this.transport);
1217
+ const colorCount = await readU16(this.transport);
1218
+ await this.transport.readExactly(colorCount * 6);
1219
+ throw new SandboxError(
1220
+ "desktop sessions do not support color-map VNC pixel formats"
1221
+ );
1222
+ }
1223
+ async readServerCutText() {
1224
+ await this.transport.readExactly(3);
1225
+ const length = await readU32(this.transport);
1226
+ await this.transport.readExactly(length);
1227
+ }
1228
+ resizeFramebuffer(width, height) {
1229
+ this.width = width;
1230
+ this.height = height;
1231
+ this.framebuffer = allocateFramebuffer(width, height);
1232
+ this.pointerX = width > 0 ? Math.min(this.pointerX, width - 1) : 0;
1233
+ this.pointerY = height > 0 ? Math.min(this.pointerY, height - 1) : 0;
1234
+ }
1235
+ blitRawRectangle(x, y, width, height, data) {
1236
+ if (x + width > this.width || y + height > this.height) {
1237
+ throw new SandboxError("desktop raw rectangle exceeds framebuffer bounds");
1238
+ }
1239
+ const bytesPerPixel = this.pixelFormat.bytesPerPixel();
1240
+ for (let row = 0; row < height; row += 1) {
1241
+ for (let col = 0; col < width; col += 1) {
1242
+ const srcIndex = (row * width + col) * bytesPerPixel;
1243
+ const rgba = this.pixelFormat.decodePixel(
1244
+ data.subarray(srcIndex, srcIndex + bytesPerPixel)
1245
+ );
1246
+ const dstIndex = ((y + row) * this.width + x + col) * 4;
1247
+ this.framebuffer.set(rgba, dstIndex);
1248
+ }
1249
+ }
1250
+ }
1251
+ };
1252
+ var Desktop = class _Desktop {
1253
+ session;
1254
+ connectRequest;
1255
+ operationChain = Promise.resolve();
1256
+ reconnectPromise = null;
1257
+ closed = false;
1258
+ constructor(session, connectRequest) {
1259
+ this.session = session;
1260
+ this.connectRequest = connectRequest;
1261
+ }
1262
+ static async connect(options) {
1263
+ const connectRequest = normalizeDesktopConnectRequest(options);
1264
+ const session = await openDesktopSession(connectRequest);
1265
+ return new _Desktop(session, connectRequest);
1266
+ }
1267
+ get width() {
1268
+ return this.session.width;
1269
+ }
1270
+ get height() {
1271
+ return this.session.height;
1272
+ }
1273
+ async close() {
1274
+ this.closed = true;
1275
+ await this.enqueue(() => this.session.close());
1276
+ }
1277
+ async screenshot(timeout = 5) {
1278
+ return this.enqueue(() => this.captureScreenshot(timeout));
1279
+ }
1280
+ getFrameVersion() {
1281
+ return this.session.getFrameVersion();
1282
+ }
1283
+ async screenshotAfter(frameVersion, timeout = 1) {
1284
+ return this.enqueue(() => this.captureScreenshotAfter(frameVersion, timeout));
1285
+ }
1286
+ async moveMouse(x, y) {
1287
+ await this.enqueue(() => this.session.moveMouse(x, y));
1288
+ }
1289
+ async mousePress(options) {
1290
+ await this.enqueue(() => this.session.mousePress(options));
1291
+ }
1292
+ async mouseRelease(options) {
1293
+ await this.enqueue(() => this.session.mouseRelease(options));
1294
+ }
1295
+ async click(options) {
1296
+ await this.enqueue(() => this.session.click(options));
1297
+ }
1298
+ async doubleClick(options) {
1299
+ await this.enqueue(() => this.session.doubleClick(options));
1300
+ }
1301
+ async leftClick(x, y) {
1302
+ await this.click({ button: "left", x, y });
1303
+ }
1304
+ async middleClick(x, y) {
1305
+ await this.click({ button: "middle", x, y });
1306
+ }
1307
+ async rightClick(x, y) {
1308
+ await this.click({ button: "right", x, y });
1309
+ }
1310
+ async scroll(steps, x, y) {
1311
+ await this.enqueue(() => this.session.scroll(steps, x, y));
1312
+ }
1313
+ async scrollUp(steps = 1, x, y) {
1314
+ await this.scroll(Math.abs(steps), x, y);
1315
+ }
1316
+ async scrollDown(steps = 1, x, y) {
1317
+ await this.scroll(-Math.abs(steps), x, y);
1318
+ }
1319
+ async keyDown(key) {
1320
+ await this.enqueue(() => this.session.keyDown(key));
1321
+ }
1322
+ async keyUp(key) {
1323
+ await this.enqueue(() => this.session.keyUp(key));
1324
+ }
1325
+ async press(keys) {
1326
+ await this.enqueue(() => this.session.press(keys));
1327
+ }
1328
+ async typeText(text) {
1329
+ await this.enqueue(() => this.session.typeText(text));
1330
+ }
1331
+ enqueue(operation) {
1332
+ const run = this.operationChain.catch(() => {
1333
+ }).then(operation);
1334
+ this.operationChain = run.then(
1335
+ () => void 0,
1336
+ () => void 0
1337
+ );
1338
+ return run;
1339
+ }
1340
+ async captureScreenshot(timeout) {
1341
+ try {
1342
+ return await this.session.screenshot(timeout);
1343
+ } catch (error) {
1344
+ if (!isReconnectableDesktopScreenshotError(error) || this.closed) {
1345
+ throw error;
1346
+ }
1347
+ await this.reconnect();
1348
+ return this.session.screenshot(timeout);
1349
+ }
1350
+ }
1351
+ async captureScreenshotAfter(frameVersion, timeout) {
1352
+ try {
1353
+ return await this.session.screenshotAfter(frameVersion, timeout);
1354
+ } catch (error) {
1355
+ if (!isReconnectableDesktopScreenshotError(error) || this.closed) {
1356
+ throw error;
1357
+ }
1358
+ await this.reconnect();
1359
+ return this.session.screenshot(timeout);
1360
+ }
1361
+ }
1362
+ async reconnect() {
1363
+ if (this.reconnectPromise) {
1364
+ return this.reconnectPromise;
1365
+ }
1366
+ this.reconnectPromise = this.performReconnect().finally(() => {
1367
+ this.reconnectPromise = null;
1368
+ });
1369
+ return this.reconnectPromise;
1370
+ }
1371
+ async performReconnect() {
1372
+ const previousSession = this.session;
1373
+ await previousSession.close().catch(() => {
1374
+ });
1375
+ this.session = await openDesktopSession(this.connectRequest);
1376
+ }
1377
+ };
1378
+ var ProtocolVersion = class _ProtocolVersion {
1379
+ major;
1380
+ minor;
1381
+ constructor(major, minor) {
1382
+ this.major = major;
1383
+ this.minor = minor;
1384
+ }
1385
+ static async read(transport) {
1386
+ const raw = await transport.readExactly(12);
1387
+ const text = raw.toString("ascii");
1388
+ const trimmed = text.endsWith("\n") ? text.slice(0, -1) : text;
1389
+ const match = /^RFB (\d{3})\.(\d{3})$/.exec(trimmed);
1390
+ if (!match) {
1391
+ throw new SandboxError(`invalid VNC protocol banner \`${text}\``);
1392
+ }
1393
+ return new _ProtocolVersion(Number.parseInt(match[1], 10), Number.parseInt(match[2], 10));
1394
+ }
1395
+ negotiated() {
1396
+ if (this.major !== 3 || this.minor >= 8) {
1397
+ return new _ProtocolVersion(3, 8);
1398
+ }
1399
+ if (this.minor >= 7) {
1400
+ return new _ProtocolVersion(3, 7);
1401
+ }
1402
+ return new _ProtocolVersion(3, 3);
1403
+ }
1404
+ render() {
1405
+ return `RFB ${String(this.major).padStart(3, "0")}.${String(this.minor).padStart(3, "0")}
1406
+ `;
1407
+ }
1408
+ };
1409
+ var PixelFormat = class _PixelFormat {
1410
+ bitsPerPixel;
1411
+ depth;
1412
+ bigEndian;
1413
+ trueColor;
1414
+ redMax;
1415
+ greenMax;
1416
+ blueMax;
1417
+ redShift;
1418
+ greenShift;
1419
+ blueShift;
1420
+ constructor(options) {
1421
+ this.bitsPerPixel = options.bitsPerPixel;
1422
+ this.depth = options.depth;
1423
+ this.bigEndian = options.bigEndian;
1424
+ this.trueColor = options.trueColor;
1425
+ this.redMax = options.redMax;
1426
+ this.greenMax = options.greenMax;
1427
+ this.blueMax = options.blueMax;
1428
+ this.redShift = options.redShift;
1429
+ this.greenShift = options.greenShift;
1430
+ this.blueShift = options.blueShift;
1431
+ }
1432
+ static preferred() {
1433
+ return new _PixelFormat({
1434
+ bitsPerPixel: 32,
1435
+ depth: 24,
1436
+ bigEndian: false,
1437
+ trueColor: true,
1438
+ redMax: 255,
1439
+ greenMax: 255,
1440
+ blueMax: 255,
1441
+ redShift: 16,
1442
+ greenShift: 8,
1443
+ blueShift: 0
1444
+ });
1445
+ }
1446
+ static parse(bytes) {
1447
+ if (bytes.length !== 16) {
1448
+ throw new SandboxError("invalid VNC pixel format payload length");
1449
+ }
1450
+ return new _PixelFormat({
1451
+ bitsPerPixel: bytes[0],
1452
+ depth: bytes[1],
1453
+ bigEndian: bytes[2] !== 0,
1454
+ trueColor: bytes[3] !== 0,
1455
+ redMax: Buffer.from(bytes.subarray(4, 6)).readUInt16BE(0),
1456
+ greenMax: Buffer.from(bytes.subarray(6, 8)).readUInt16BE(0),
1457
+ blueMax: Buffer.from(bytes.subarray(8, 10)).readUInt16BE(0),
1458
+ redShift: bytes[10],
1459
+ greenShift: bytes[11],
1460
+ blueShift: bytes[12]
1461
+ });
1462
+ }
1463
+ bytesPerPixel() {
1464
+ return this.bitsPerPixel / 8;
1465
+ }
1466
+ encode() {
1467
+ const bytes = Buffer.alloc(16);
1468
+ bytes[0] = this.bitsPerPixel;
1469
+ bytes[1] = this.depth;
1470
+ bytes[2] = this.bigEndian ? 1 : 0;
1471
+ bytes[3] = this.trueColor ? 1 : 0;
1472
+ bytes.writeUInt16BE(this.redMax, 4);
1473
+ bytes.writeUInt16BE(this.greenMax, 6);
1474
+ bytes.writeUInt16BE(this.blueMax, 8);
1475
+ bytes[10] = this.redShift;
1476
+ bytes[11] = this.greenShift;
1477
+ bytes[12] = this.blueShift;
1478
+ return bytes;
1479
+ }
1480
+ decodePixel(bytes) {
1481
+ if (bytes.length !== this.bytesPerPixel()) {
1482
+ throw new SandboxError("desktop pixel buffer has an unexpected size");
1483
+ }
1484
+ let value = 0;
1485
+ if (this.bigEndian) {
1486
+ for (const byte of bytes) {
1487
+ value = value << 8 | byte;
1488
+ }
1489
+ } else {
1490
+ for (let index = 0; index < bytes.length; index += 1) {
1491
+ value |= bytes[index] << index * 8;
1492
+ }
1493
+ }
1494
+ const red = scaleChannel(value >> this.redShift & this.redMax, this.redMax);
1495
+ const green = scaleChannel(
1496
+ value >> this.greenShift & this.greenMax,
1497
+ this.greenMax
1498
+ );
1499
+ const blue = scaleChannel(value >> this.blueShift & this.blueMax, this.blueMax);
1500
+ return Uint8Array.of(red, green, blue, 255);
1501
+ }
1502
+ };
1503
+ var ServerInit = class _ServerInit {
1504
+ width;
1505
+ height;
1506
+ pixelFormat;
1507
+ constructor(width, height, pixelFormat) {
1508
+ this.width = width;
1509
+ this.height = height;
1510
+ this.pixelFormat = pixelFormat;
1511
+ }
1512
+ static async read(transport) {
1513
+ const width = await readU16(transport);
1514
+ const height = await readU16(transport);
1515
+ const pixelFormat = PixelFormat.parse(await transport.readExactly(16));
1516
+ const nameLength = await readU32(transport);
1517
+ await transport.readExactly(nameLength);
1518
+ return new _ServerInit(width, height, pixelFormat);
1519
+ }
1520
+ };
1521
+ async function negotiateSecurity(transport, version, password) {
1522
+ let securityTypes;
1523
+ if (version.minor === 3) {
1524
+ const securityType = await readU32(transport);
1525
+ if (securityType === 0) {
1526
+ const reasonLength = await readU32(transport);
1527
+ const reason = (await transport.readExactly(reasonLength)).toString("utf8");
1528
+ throw new SandboxError(`VNC security negotiation failed: ${reason}`);
1529
+ }
1530
+ securityTypes = [securityType];
1531
+ } else {
1532
+ const count = await readU8(transport);
1533
+ if (count === 0) {
1534
+ const reasonLength = await readU32(transport);
1535
+ const reason = (await transport.readExactly(reasonLength)).toString("utf8");
1536
+ throw new SandboxError(`VNC security negotiation failed: ${reason}`);
1537
+ }
1538
+ securityTypes = [...await transport.readExactly(count)];
1539
+ }
1540
+ let selected;
1541
+ if (password != null && securityTypes.includes(SECURITY_TYPE_VNC_AUTH)) {
1542
+ selected = SECURITY_TYPE_VNC_AUTH;
1543
+ } else if (securityTypes.includes(SECURITY_TYPE_NONE)) {
1544
+ selected = SECURITY_TYPE_NONE;
1545
+ } else if (securityTypes.includes(SECURITY_TYPE_VNC_AUTH)) {
1546
+ throw new SandboxError(
1547
+ "VNC server requires password authentication but no password was provided"
1548
+ );
1549
+ } else {
1550
+ throw new SandboxError(
1551
+ `unsupported VNC security types advertised by server: [${securityTypes.join(", ")}]`
1552
+ );
1553
+ }
1554
+ if (version.minor >= 7) {
1555
+ await transport.writeAll(Uint8Array.of(selected));
1556
+ }
1557
+ if (selected === SECURITY_TYPE_VNC_AUTH) {
1558
+ if (password == null) {
1559
+ throw new SandboxError(
1560
+ "VNC server requires password authentication but no password was provided"
1561
+ );
1562
+ }
1563
+ const challenge = await transport.readExactly(16);
1564
+ const response = encryptVncChallenge(Buffer.from(password, "utf8"), challenge);
1565
+ await transport.writeAll(response);
1566
+ await readSecurityResult(transport, version.minor >= 8);
1567
+ } else if (version.minor >= 8) {
1568
+ await readSecurityResult(transport, true);
1569
+ }
1570
+ return selected;
1571
+ }
1572
+ function encryptVncChallenge(password, challenge) {
1573
+ if (challenge.length !== 16) {
1574
+ throw new SandboxError("VNC authentication challenge must be 16 bytes");
1575
+ }
1576
+ const key = Buffer.alloc(8);
1577
+ for (let index = 0; index < Math.min(password.length, 8); index += 1) {
1578
+ key[index] = reverseBits(password[index]);
1579
+ }
1580
+ const roundKeys = buildDesRoundKeys(key);
1581
+ const output = Buffer.alloc(16);
1582
+ for (let blockIndex = 0; blockIndex < 2; blockIndex += 1) {
1583
+ const start = blockIndex * 8;
1584
+ const encrypted = encryptDesBlock(challenge.subarray(start, start + 8), roundKeys);
1585
+ output.set(encrypted, start);
1586
+ }
1587
+ return output;
1588
+ }
1589
+ function reverseBits(value) {
1590
+ let reversed = 0;
1591
+ for (let bit = 0; bit < 8; bit += 1) {
1592
+ reversed |= (value >> bit & 1) << 7 - bit;
1593
+ }
1594
+ return reversed;
1595
+ }
1596
+ async function readSecurityResult(transport, hasReasonString) {
1597
+ const status = await readU32(transport);
1598
+ if (status === 0) {
1599
+ return;
1600
+ }
1601
+ let reason;
1602
+ if (hasReasonString) {
1603
+ const reasonLength = await readU32(transport);
1604
+ reason = (await transport.readExactly(reasonLength)).toString("utf8");
1605
+ } else if (status === 1) {
1606
+ reason = "authentication failed";
1607
+ } else {
1608
+ reason = `security handshake failed with status ${status}`;
1609
+ }
1610
+ throw new SandboxError(`VNC security negotiation failed: ${reason}`);
1611
+ }
1612
+ async function sendSetPixelFormat(transport, pixelFormat) {
1613
+ const message = Buffer.alloc(20);
1614
+ message[0] = 0;
1615
+ message.set(pixelFormat.encode(), 4);
1616
+ await transport.writeAll(message);
1617
+ }
1618
+ async function sendSetEncodings(transport, encodings) {
1619
+ const message = Buffer.alloc(4 + encodings.length * 4);
1620
+ message[0] = 2;
1621
+ message.writeUInt16BE(encodings.length, 2);
1622
+ for (let index = 0; index < encodings.length; index += 1) {
1623
+ message.writeInt32BE(encodings[index], 4 + index * 4);
1624
+ }
1625
+ await transport.writeAll(message);
1626
+ }
1627
+ async function sendFramebufferUpdateRequest(transport, incremental, x, y, width, height) {
1628
+ const message = Buffer.alloc(10);
1629
+ message[0] = 3;
1630
+ message[1] = incremental ? 1 : 0;
1631
+ message.writeUInt16BE(x, 2);
1632
+ message.writeUInt16BE(y, 4);
1633
+ message.writeUInt16BE(width, 6);
1634
+ message.writeUInt16BE(height, 8);
1635
+ await transport.writeAll(message);
1636
+ }
1637
+ async function sendPointerEvent(transport, buttonMask2, x, y) {
1638
+ const message = Buffer.alloc(6);
1639
+ message[0] = 5;
1640
+ message[1] = buttonMask2;
1641
+ message.writeUInt16BE(x, 2);
1642
+ message.writeUInt16BE(y, 4);
1643
+ await transport.writeAll(message);
1644
+ }
1645
+ async function sendKeyEvent(transport, down, keysym) {
1646
+ const message = Buffer.alloc(8);
1647
+ message[0] = 4;
1648
+ message[1] = down ? 1 : 0;
1649
+ message.writeUInt32BE(keysym, 4);
1650
+ await transport.writeAll(message);
1651
+ }
1652
+ async function readU8(transport) {
1653
+ return (await transport.readExactly(1))[0];
1654
+ }
1655
+ async function readU16(transport) {
1656
+ return (await transport.readExactly(2)).readUInt16BE(0);
1657
+ }
1658
+ async function readU32(transport) {
1659
+ return (await transport.readExactly(4)).readUInt32BE(0);
1660
+ }
1661
+ async function readI32(transport) {
1662
+ return (await transport.readExactly(4)).readInt32BE(0);
1663
+ }
1664
+ function scaleChannel(value, max) {
1665
+ if (max === 0) {
1666
+ throw new SandboxError("invalid VNC pixel format with zero channel range");
1667
+ }
1668
+ return Math.trunc(value * 255 / max);
1669
+ }
1670
+ function allocateFramebuffer(width, height) {
1671
+ return new Uint8Array(width * height * 4);
1672
+ }
1673
+ function normalizeDesktopConnectRequest(options) {
1674
+ return {
1675
+ ...options,
1676
+ port: validatePort2(options.port ?? 5901, "desktop port"),
1677
+ shared: options.shared ?? true,
1678
+ connectTimeout: options.connectTimeout ?? 10
1679
+ };
1680
+ }
1681
+ async function openDesktopSession(options) {
1682
+ const connectTimeoutMs = secondsToMillis2(options.connectTimeout);
1683
+ const state = {};
1684
+ try {
1685
+ return await withTimeout(
1686
+ connectTimeoutMs,
1687
+ async () => {
1688
+ state.transport = await TunnelByteStream.connect({
1689
+ baseUrl: options.baseUrl,
1690
+ wsHeaders: options.wsHeaders,
1691
+ remotePort: options.port,
1692
+ connectTimeoutMs
1693
+ });
1694
+ return DesktopSession.connect(
1695
+ state.transport,
1696
+ options.password,
1697
+ options.shared
1698
+ );
1699
+ },
1700
+ `timed out while connecting desktop session after ${options.connectTimeout.toFixed(2)}s`
1701
+ );
1702
+ } catch (error) {
1703
+ if (state.transport) {
1704
+ await state.transport.close().catch(() => {
1705
+ });
1706
+ }
1707
+ throw error;
1708
+ }
1709
+ }
1710
+ function isReconnectableDesktopScreenshotError(error) {
1711
+ if (!(error instanceof Error)) {
1712
+ return false;
1713
+ }
1714
+ const message = error.message.toLowerCase();
1715
+ return message.includes("desktop tunnel closed unexpectedly") || message.includes("desktop tunnel is not connected") || message.includes("connection closed") || message.includes("econnreset") || message.includes("timed out waiting for initial desktop framebuffer") || message.includes("timed out while connecting tunnel websocket") || message.includes("tunnel websocket closed before opening") || message.includes("tunnel websocket handshake failed");
1716
+ }
1717
+ function createDeferredSignal() {
1718
+ let resolveCurrent;
1719
+ let promise = new Promise((resolve) => {
1720
+ resolveCurrent = resolve;
1721
+ });
1722
+ return {
1723
+ resolve() {
1724
+ resolveCurrent();
1725
+ promise = new Promise((resolve) => {
1726
+ resolveCurrent = resolve;
1727
+ });
1728
+ },
1729
+ wait() {
1730
+ return promise;
1731
+ }
1732
+ };
1733
+ }
1734
+ function normalizeError(error) {
1735
+ return error instanceof Error ? error : new SandboxError(String(error));
1736
+ }
1737
+ function buttonMask(button) {
1738
+ const normalized = button.trim().toLowerCase();
1739
+ if (normalized === "left") return BUTTON_LEFT_MASK;
1740
+ if (normalized === "middle") return BUTTON_MIDDLE_MASK;
1741
+ if (normalized === "right") return BUTTON_RIGHT_MASK;
1742
+ throw new SandboxError(
1743
+ `unsupported mouse button \`${button}\`; expected left, middle, or right`
1744
+ );
1745
+ }
1746
+ function keysymFromKeyName(key) {
1747
+ const trimmed = key.trim();
1748
+ if (trimmed.length === 0) {
1749
+ throw new SandboxError("desktop key name cannot be empty");
1750
+ }
1751
+ if ([...trimmed].length === 1) {
1752
+ return keysymFromChar(trimmed);
1753
+ }
1754
+ const normalized = trimmed.toLowerCase();
1755
+ const special = SPECIAL_KEYSYMS.get(normalized);
1756
+ if (special != null) {
1757
+ return special;
1758
+ }
1759
+ const functionMatch = /^f([1-9]|1[0-2])$/.exec(normalized);
1760
+ if (functionMatch) {
1761
+ return 65469 + Number.parseInt(functionMatch[1], 10);
1762
+ }
1763
+ throw new SandboxError(`unsupported desktop key \`${trimmed}\``);
1764
+ }
1765
+ function keysymFromChar(char) {
1766
+ const codePoint = char.codePointAt(0);
1767
+ if (codePoint == null) {
1768
+ throw new SandboxError("desktop key name cannot be empty");
1769
+ }
1770
+ if (char === "\n" || char === "\r") return 65293;
1771
+ if (char === " ") return 65289;
1772
+ if (char === "\b") return 65288;
1773
+ if (codePoint >= 32 && codePoint <= 126) return codePoint;
1774
+ if (codePoint < 32 || codePoint >= 127 && codePoint <= 159) {
1775
+ throw new SandboxError(
1776
+ `unsupported control character U+${codePoint.toString(16).toUpperCase().padStart(4, "0")} for desktop typing`
1777
+ );
1778
+ }
1779
+ return 16777216 | codePoint;
1780
+ }
1781
+ function encodePng(width, height, rgba) {
1782
+ const stride = width * 4;
1783
+ const raw = Buffer.alloc((stride + 1) * height);
1784
+ for (let row = 0; row < height; row += 1) {
1785
+ const srcOffset = row * stride;
1786
+ const dstOffset = row * (stride + 1);
1787
+ raw[dstOffset] = 0;
1788
+ raw.set(rgba.subarray(srcOffset, srcOffset + stride), dstOffset + 1);
1789
+ }
1790
+ const ihdr = Buffer.alloc(13);
1791
+ ihdr.writeUInt32BE(width, 0);
1792
+ ihdr.writeUInt32BE(height, 4);
1793
+ ihdr[8] = 8;
1794
+ ihdr[9] = 6;
1795
+ ihdr[10] = 0;
1796
+ ihdr[11] = 0;
1797
+ ihdr[12] = 0;
1798
+ const idat = (0, import_node_zlib.deflateSync)(raw);
1799
+ return Buffer.concat([
1800
+ PNG_SIGNATURE,
1801
+ pngChunk("IHDR", ihdr),
1802
+ pngChunk("IDAT", idat),
1803
+ pngChunk("IEND", Buffer.alloc(0))
1804
+ ]);
1805
+ }
1806
+ function pngChunk(type, data) {
1807
+ const chunkType = Buffer.from(type, "ascii");
1808
+ const length = Buffer.alloc(4);
1809
+ length.writeUInt32BE(data.length, 0);
1810
+ const crc = Buffer.alloc(4);
1811
+ crc.writeUInt32BE(crc32(Buffer.concat([chunkType, data])), 0);
1812
+ return Buffer.concat([length, chunkType, data, crc]);
1813
+ }
1814
+ function crc32(data) {
1815
+ let crc = 4294967295;
1816
+ for (const byte of data) {
1817
+ crc = CRC32_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
1818
+ }
1819
+ return (crc ^ 4294967295) >>> 0;
1820
+ }
1821
+ function buildCrc32Table() {
1822
+ const table = new Uint32Array(256);
1823
+ for (let index = 0; index < 256; index += 1) {
1824
+ let value = index;
1825
+ for (let bit = 0; bit < 8; bit += 1) {
1826
+ value = (value & 1) !== 0 ? 3988292384 ^ value >>> 1 : value >>> 1;
1827
+ }
1828
+ table[index] = value >>> 0;
1829
+ }
1830
+ return table;
1831
+ }
1832
+ function validateCoordinate(value, label) {
1833
+ return validateIntegerInRange(value, label, 0, 65535);
1834
+ }
1835
+ function validatePort2(value, label) {
1836
+ return validateIntegerInRange(value, label, 1, 65535);
1837
+ }
1838
+ function validateIntegerInRange(value, label, min, max) {
1839
+ if (!Number.isInteger(value) || value < min || value > max) {
1840
+ throw new SandboxError(`${label} must be an integer between ${min} and ${max}, got ${value}`);
1841
+ }
1842
+ return value;
1843
+ }
1844
+ function validateInteger(value, label) {
1845
+ if (!Number.isInteger(value)) {
1846
+ throw new SandboxError(`${label} must be an integer, got ${value}`);
1847
+ }
1848
+ return value;
1849
+ }
1850
+ function validateNonNegativeInteger(value, label) {
1851
+ if (!Number.isInteger(value) || value < 0) {
1852
+ throw new SandboxError(`${label} must be a non-negative integer, got ${value}`);
1853
+ }
1854
+ return value;
1855
+ }
1856
+ function secondsToMillis2(seconds) {
1857
+ if (!Number.isFinite(seconds) || seconds < 0) {
1858
+ throw new SandboxError(`timeout must be >= 0 seconds, got ${seconds}`);
1859
+ }
1860
+ return Math.round(seconds * 1e3);
1861
+ }
1862
+ var SPECIAL_KEYSYMS = /* @__PURE__ */ new Map([
1863
+ ["enter", 65293],
1864
+ ["tab", 65289],
1865
+ ["escape", 65307],
1866
+ ["backspace", 65288],
1867
+ ["delete", 65535],
1868
+ ["space", 32],
1869
+ ["up", 65362],
1870
+ ["down", 65364],
1871
+ ["left", 65361],
1872
+ ["right", 65363],
1873
+ ["home", 65360],
1874
+ ["end", 65367],
1875
+ ["pageup", 65365],
1876
+ ["pagedown", 65366],
1877
+ ["page_up", 65365],
1878
+ ["page_down", 65366],
1879
+ ["shift", 65505],
1880
+ ["ctrl", 65507],
1881
+ ["control", 65507],
1882
+ ["alt", 65513],
1883
+ ["meta", 65511]
1884
+ ]);
1885
+ var DES_INITIAL_PERMUTATION = [
1886
+ 58,
1887
+ 50,
1888
+ 42,
1889
+ 34,
1890
+ 26,
1891
+ 18,
1892
+ 10,
1893
+ 2,
1894
+ 60,
1895
+ 52,
1896
+ 44,
1897
+ 36,
1898
+ 28,
1899
+ 20,
1900
+ 12,
1901
+ 4,
1902
+ 62,
1903
+ 54,
1904
+ 46,
1905
+ 38,
1906
+ 30,
1907
+ 22,
1908
+ 14,
1909
+ 6,
1910
+ 64,
1911
+ 56,
1912
+ 48,
1913
+ 40,
1914
+ 32,
1915
+ 24,
1916
+ 16,
1917
+ 8,
1918
+ 57,
1919
+ 49,
1920
+ 41,
1921
+ 33,
1922
+ 25,
1923
+ 17,
1924
+ 9,
1925
+ 1,
1926
+ 59,
1927
+ 51,
1928
+ 43,
1929
+ 35,
1930
+ 27,
1931
+ 19,
1932
+ 11,
1933
+ 3,
1934
+ 61,
1935
+ 53,
1936
+ 45,
1937
+ 37,
1938
+ 29,
1939
+ 21,
1940
+ 13,
1941
+ 5,
1942
+ 63,
1943
+ 55,
1944
+ 47,
1945
+ 39,
1946
+ 31,
1947
+ 23,
1948
+ 15,
1949
+ 7
1950
+ ];
1951
+ var DES_FINAL_PERMUTATION = [
1952
+ 40,
1953
+ 8,
1954
+ 48,
1955
+ 16,
1956
+ 56,
1957
+ 24,
1958
+ 64,
1959
+ 32,
1960
+ 39,
1961
+ 7,
1962
+ 47,
1963
+ 15,
1964
+ 55,
1965
+ 23,
1966
+ 63,
1967
+ 31,
1968
+ 38,
1969
+ 6,
1970
+ 46,
1971
+ 14,
1972
+ 54,
1973
+ 22,
1974
+ 62,
1975
+ 30,
1976
+ 37,
1977
+ 5,
1978
+ 45,
1979
+ 13,
1980
+ 53,
1981
+ 21,
1982
+ 61,
1983
+ 29,
1984
+ 36,
1985
+ 4,
1986
+ 44,
1987
+ 12,
1988
+ 52,
1989
+ 20,
1990
+ 60,
1991
+ 28,
1992
+ 35,
1993
+ 3,
1994
+ 43,
1995
+ 11,
1996
+ 51,
1997
+ 19,
1998
+ 59,
1999
+ 27,
2000
+ 34,
2001
+ 2,
2002
+ 42,
2003
+ 10,
2004
+ 50,
2005
+ 18,
2006
+ 58,
2007
+ 26,
2008
+ 33,
2009
+ 1,
2010
+ 41,
2011
+ 9,
2012
+ 49,
2013
+ 17,
2014
+ 57,
2015
+ 25
2016
+ ];
2017
+ var DES_EXPANSION = [
2018
+ 32,
2019
+ 1,
2020
+ 2,
2021
+ 3,
2022
+ 4,
2023
+ 5,
2024
+ 4,
2025
+ 5,
2026
+ 6,
2027
+ 7,
2028
+ 8,
2029
+ 9,
2030
+ 8,
2031
+ 9,
2032
+ 10,
2033
+ 11,
2034
+ 12,
2035
+ 13,
2036
+ 12,
2037
+ 13,
2038
+ 14,
2039
+ 15,
2040
+ 16,
2041
+ 17,
2042
+ 16,
2043
+ 17,
2044
+ 18,
2045
+ 19,
2046
+ 20,
2047
+ 21,
2048
+ 20,
2049
+ 21,
2050
+ 22,
2051
+ 23,
2052
+ 24,
2053
+ 25,
2054
+ 24,
2055
+ 25,
2056
+ 26,
2057
+ 27,
2058
+ 28,
2059
+ 29,
2060
+ 28,
2061
+ 29,
2062
+ 30,
2063
+ 31,
2064
+ 32,
2065
+ 1
2066
+ ];
2067
+ var DES_P_PERMUTATION = [
2068
+ 16,
2069
+ 7,
2070
+ 20,
2071
+ 21,
2072
+ 29,
2073
+ 12,
2074
+ 28,
2075
+ 17,
2076
+ 1,
2077
+ 15,
2078
+ 23,
2079
+ 26,
2080
+ 5,
2081
+ 18,
2082
+ 31,
2083
+ 10,
2084
+ 2,
2085
+ 8,
2086
+ 24,
2087
+ 14,
2088
+ 32,
2089
+ 27,
2090
+ 3,
2091
+ 9,
2092
+ 19,
2093
+ 13,
2094
+ 30,
2095
+ 6,
2096
+ 22,
2097
+ 11,
2098
+ 4,
2099
+ 25
2100
+ ];
2101
+ var DES_PC1 = [
2102
+ 57,
2103
+ 49,
2104
+ 41,
2105
+ 33,
2106
+ 25,
2107
+ 17,
2108
+ 9,
2109
+ 1,
2110
+ 58,
2111
+ 50,
2112
+ 42,
2113
+ 34,
2114
+ 26,
2115
+ 18,
2116
+ 10,
2117
+ 2,
2118
+ 59,
2119
+ 51,
2120
+ 43,
2121
+ 35,
2122
+ 27,
2123
+ 19,
2124
+ 11,
2125
+ 3,
2126
+ 60,
2127
+ 52,
2128
+ 44,
2129
+ 36,
2130
+ 63,
2131
+ 55,
2132
+ 47,
2133
+ 39,
2134
+ 31,
2135
+ 23,
2136
+ 15,
2137
+ 7,
2138
+ 62,
2139
+ 54,
2140
+ 46,
2141
+ 38,
2142
+ 30,
2143
+ 22,
2144
+ 14,
2145
+ 6,
2146
+ 61,
2147
+ 53,
2148
+ 45,
2149
+ 37,
2150
+ 29,
2151
+ 21,
2152
+ 13,
2153
+ 5,
2154
+ 28,
2155
+ 20,
2156
+ 12,
2157
+ 4
2158
+ ];
2159
+ var DES_PC2 = [
2160
+ 14,
2161
+ 17,
2162
+ 11,
2163
+ 24,
2164
+ 1,
2165
+ 5,
2166
+ 3,
2167
+ 28,
2168
+ 15,
2169
+ 6,
2170
+ 21,
2171
+ 10,
2172
+ 23,
2173
+ 19,
2174
+ 12,
2175
+ 4,
2176
+ 26,
2177
+ 8,
2178
+ 16,
2179
+ 7,
2180
+ 27,
2181
+ 20,
2182
+ 13,
2183
+ 2,
2184
+ 41,
2185
+ 52,
2186
+ 31,
2187
+ 37,
2188
+ 47,
2189
+ 55,
2190
+ 30,
2191
+ 40,
2192
+ 51,
2193
+ 45,
2194
+ 33,
2195
+ 48,
2196
+ 44,
2197
+ 49,
2198
+ 39,
2199
+ 56,
2200
+ 34,
2201
+ 53,
2202
+ 46,
2203
+ 42,
2204
+ 50,
2205
+ 36,
2206
+ 29,
2207
+ 32
2208
+ ];
2209
+ var DES_ROTATIONS = [
2210
+ 1,
2211
+ 1,
2212
+ 2,
2213
+ 2,
2214
+ 2,
2215
+ 2,
2216
+ 2,
2217
+ 2,
2218
+ 1,
2219
+ 2,
2220
+ 2,
2221
+ 2,
2222
+ 2,
2223
+ 2,
2224
+ 2,
2225
+ 1
2226
+ ];
2227
+ var DES_SBOXES = [
2228
+ [
2229
+ 14,
2230
+ 4,
2231
+ 13,
2232
+ 1,
2233
+ 2,
2234
+ 15,
2235
+ 11,
2236
+ 8,
2237
+ 3,
2238
+ 10,
2239
+ 6,
2240
+ 12,
2241
+ 5,
2242
+ 9,
2243
+ 0,
2244
+ 7,
2245
+ 0,
2246
+ 15,
2247
+ 7,
2248
+ 4,
2249
+ 14,
2250
+ 2,
2251
+ 13,
2252
+ 1,
2253
+ 10,
2254
+ 6,
2255
+ 12,
2256
+ 11,
2257
+ 9,
2258
+ 5,
2259
+ 3,
2260
+ 8,
2261
+ 4,
2262
+ 1,
2263
+ 14,
2264
+ 8,
2265
+ 13,
2266
+ 6,
2267
+ 2,
2268
+ 11,
2269
+ 15,
2270
+ 12,
2271
+ 9,
2272
+ 7,
2273
+ 3,
2274
+ 10,
2275
+ 5,
2276
+ 0,
2277
+ 15,
2278
+ 12,
2279
+ 8,
2280
+ 2,
2281
+ 4,
2282
+ 9,
2283
+ 1,
2284
+ 7,
2285
+ 5,
2286
+ 11,
2287
+ 3,
2288
+ 14,
2289
+ 10,
2290
+ 0,
2291
+ 6,
2292
+ 13
2293
+ ],
2294
+ [
2295
+ 15,
2296
+ 1,
2297
+ 8,
2298
+ 14,
2299
+ 6,
2300
+ 11,
2301
+ 3,
2302
+ 4,
2303
+ 9,
2304
+ 7,
2305
+ 2,
2306
+ 13,
2307
+ 12,
2308
+ 0,
2309
+ 5,
2310
+ 10,
2311
+ 3,
2312
+ 13,
2313
+ 4,
2314
+ 7,
2315
+ 15,
2316
+ 2,
2317
+ 8,
2318
+ 14,
2319
+ 12,
2320
+ 0,
2321
+ 1,
2322
+ 10,
2323
+ 6,
2324
+ 9,
2325
+ 11,
2326
+ 5,
2327
+ 0,
2328
+ 14,
2329
+ 7,
2330
+ 11,
2331
+ 10,
2332
+ 4,
2333
+ 13,
2334
+ 1,
2335
+ 5,
2336
+ 8,
2337
+ 12,
2338
+ 6,
2339
+ 9,
2340
+ 3,
2341
+ 2,
2342
+ 15,
2343
+ 13,
2344
+ 8,
2345
+ 10,
2346
+ 1,
2347
+ 3,
2348
+ 15,
2349
+ 4,
2350
+ 2,
2351
+ 11,
2352
+ 6,
2353
+ 7,
2354
+ 12,
2355
+ 0,
2356
+ 5,
2357
+ 14,
2358
+ 9
2359
+ ],
2360
+ [
2361
+ 10,
2362
+ 0,
2363
+ 9,
2364
+ 14,
2365
+ 6,
2366
+ 3,
2367
+ 15,
2368
+ 5,
2369
+ 1,
2370
+ 13,
2371
+ 12,
2372
+ 7,
2373
+ 11,
2374
+ 4,
2375
+ 2,
2376
+ 8,
2377
+ 13,
2378
+ 7,
2379
+ 0,
2380
+ 9,
2381
+ 3,
2382
+ 4,
2383
+ 6,
2384
+ 10,
2385
+ 2,
2386
+ 8,
2387
+ 5,
2388
+ 14,
2389
+ 12,
2390
+ 11,
2391
+ 15,
2392
+ 1,
2393
+ 13,
2394
+ 6,
2395
+ 4,
2396
+ 9,
2397
+ 8,
2398
+ 15,
2399
+ 3,
2400
+ 0,
2401
+ 11,
2402
+ 1,
2403
+ 2,
2404
+ 12,
2405
+ 5,
2406
+ 10,
2407
+ 14,
2408
+ 7,
2409
+ 1,
2410
+ 10,
2411
+ 13,
2412
+ 0,
2413
+ 6,
2414
+ 9,
2415
+ 8,
2416
+ 7,
2417
+ 4,
2418
+ 15,
2419
+ 14,
2420
+ 3,
2421
+ 11,
2422
+ 5,
2423
+ 2,
2424
+ 12
2425
+ ],
2426
+ [
2427
+ 7,
2428
+ 13,
2429
+ 14,
2430
+ 3,
2431
+ 0,
2432
+ 6,
2433
+ 9,
2434
+ 10,
2435
+ 1,
2436
+ 2,
2437
+ 8,
2438
+ 5,
2439
+ 11,
2440
+ 12,
2441
+ 4,
2442
+ 15,
2443
+ 13,
2444
+ 8,
2445
+ 11,
2446
+ 5,
2447
+ 6,
2448
+ 15,
2449
+ 0,
2450
+ 3,
2451
+ 4,
2452
+ 7,
2453
+ 2,
2454
+ 12,
2455
+ 1,
2456
+ 10,
2457
+ 14,
2458
+ 9,
2459
+ 10,
2460
+ 6,
2461
+ 9,
2462
+ 0,
2463
+ 12,
2464
+ 11,
2465
+ 7,
2466
+ 13,
2467
+ 15,
2468
+ 1,
2469
+ 3,
2470
+ 14,
2471
+ 5,
2472
+ 2,
2473
+ 8,
2474
+ 4,
2475
+ 3,
2476
+ 15,
2477
+ 0,
2478
+ 6,
2479
+ 10,
2480
+ 1,
2481
+ 13,
2482
+ 8,
2483
+ 9,
2484
+ 4,
2485
+ 5,
2486
+ 11,
2487
+ 12,
2488
+ 7,
2489
+ 2,
2490
+ 14
2491
+ ],
2492
+ [
2493
+ 2,
2494
+ 12,
2495
+ 4,
2496
+ 1,
2497
+ 7,
2498
+ 10,
2499
+ 11,
2500
+ 6,
2501
+ 8,
2502
+ 5,
2503
+ 3,
2504
+ 15,
2505
+ 13,
2506
+ 0,
2507
+ 14,
2508
+ 9,
2509
+ 14,
2510
+ 11,
2511
+ 2,
2512
+ 12,
2513
+ 4,
2514
+ 7,
2515
+ 13,
2516
+ 1,
2517
+ 5,
2518
+ 0,
2519
+ 15,
2520
+ 10,
2521
+ 3,
2522
+ 9,
2523
+ 8,
2524
+ 6,
2525
+ 4,
2526
+ 2,
2527
+ 1,
2528
+ 11,
2529
+ 10,
2530
+ 13,
2531
+ 7,
2532
+ 8,
2533
+ 15,
2534
+ 9,
2535
+ 12,
2536
+ 5,
2537
+ 6,
2538
+ 3,
2539
+ 0,
2540
+ 14,
2541
+ 11,
2542
+ 8,
2543
+ 12,
2544
+ 7,
2545
+ 1,
2546
+ 14,
2547
+ 2,
2548
+ 13,
2549
+ 6,
2550
+ 15,
2551
+ 0,
2552
+ 9,
2553
+ 10,
2554
+ 4,
2555
+ 5,
2556
+ 3
2557
+ ],
2558
+ [
2559
+ 12,
2560
+ 1,
2561
+ 10,
2562
+ 15,
2563
+ 9,
2564
+ 2,
2565
+ 6,
2566
+ 8,
2567
+ 0,
2568
+ 13,
2569
+ 3,
2570
+ 4,
2571
+ 14,
2572
+ 7,
2573
+ 5,
2574
+ 11,
2575
+ 10,
2576
+ 15,
2577
+ 4,
2578
+ 2,
2579
+ 7,
2580
+ 12,
2581
+ 9,
2582
+ 5,
2583
+ 6,
2584
+ 1,
2585
+ 13,
2586
+ 14,
2587
+ 0,
2588
+ 11,
2589
+ 3,
2590
+ 8,
2591
+ 9,
2592
+ 14,
2593
+ 15,
2594
+ 5,
2595
+ 2,
2596
+ 8,
2597
+ 12,
2598
+ 3,
2599
+ 7,
2600
+ 0,
2601
+ 4,
2602
+ 10,
2603
+ 1,
2604
+ 13,
2605
+ 11,
2606
+ 6,
2607
+ 4,
2608
+ 3,
2609
+ 2,
2610
+ 12,
2611
+ 9,
2612
+ 5,
2613
+ 15,
2614
+ 10,
2615
+ 11,
2616
+ 14,
2617
+ 1,
2618
+ 7,
2619
+ 6,
2620
+ 0,
2621
+ 8,
2622
+ 13
2623
+ ],
2624
+ [
2625
+ 4,
2626
+ 11,
2627
+ 2,
2628
+ 14,
2629
+ 15,
2630
+ 0,
2631
+ 8,
2632
+ 13,
2633
+ 3,
2634
+ 12,
2635
+ 9,
2636
+ 7,
2637
+ 5,
2638
+ 10,
2639
+ 6,
2640
+ 1,
2641
+ 13,
2642
+ 0,
2643
+ 11,
2644
+ 7,
2645
+ 4,
2646
+ 9,
2647
+ 1,
2648
+ 10,
2649
+ 14,
2650
+ 3,
2651
+ 5,
2652
+ 12,
2653
+ 2,
2654
+ 15,
2655
+ 8,
2656
+ 6,
2657
+ 1,
2658
+ 4,
2659
+ 11,
2660
+ 13,
2661
+ 12,
2662
+ 3,
2663
+ 7,
2664
+ 14,
2665
+ 10,
2666
+ 15,
2667
+ 6,
2668
+ 8,
2669
+ 0,
2670
+ 5,
2671
+ 9,
2672
+ 2,
2673
+ 6,
2674
+ 11,
2675
+ 13,
2676
+ 8,
2677
+ 1,
2678
+ 4,
2679
+ 10,
2680
+ 7,
2681
+ 9,
2682
+ 5,
2683
+ 0,
2684
+ 15,
2685
+ 14,
2686
+ 2,
2687
+ 3,
2688
+ 12
2689
+ ],
2690
+ [
2691
+ 13,
2692
+ 2,
2693
+ 8,
2694
+ 4,
2695
+ 6,
2696
+ 15,
2697
+ 11,
2698
+ 1,
2699
+ 10,
2700
+ 9,
2701
+ 3,
2702
+ 14,
2703
+ 5,
2704
+ 0,
2705
+ 12,
2706
+ 7,
2707
+ 1,
2708
+ 15,
2709
+ 13,
2710
+ 8,
2711
+ 10,
2712
+ 3,
2713
+ 7,
2714
+ 4,
2715
+ 12,
2716
+ 5,
2717
+ 6,
2718
+ 11,
2719
+ 0,
2720
+ 14,
2721
+ 9,
2722
+ 2,
2723
+ 7,
2724
+ 11,
2725
+ 4,
2726
+ 1,
2727
+ 9,
2728
+ 12,
2729
+ 14,
2730
+ 2,
2731
+ 0,
2732
+ 6,
2733
+ 10,
2734
+ 13,
2735
+ 15,
2736
+ 3,
2737
+ 5,
2738
+ 8,
2739
+ 2,
2740
+ 1,
2741
+ 14,
2742
+ 7,
2743
+ 4,
2744
+ 10,
2745
+ 8,
2746
+ 13,
2747
+ 15,
2748
+ 12,
2749
+ 9,
2750
+ 0,
2751
+ 3,
2752
+ 5,
2753
+ 6,
2754
+ 11
2755
+ ]
2756
+ ];
2757
+ function buildDesRoundKeys(key) {
2758
+ const keyBlock = bytesToBigInt(key);
2759
+ const permuted = permuteBits(keyBlock, DES_PC1, 64);
2760
+ let c = Number(permuted >> 28n & 0x0fffffffn);
2761
+ let d = Number(permuted & 0x0fffffffn);
2762
+ const roundKeys = [];
2763
+ for (const rotation of DES_ROTATIONS) {
2764
+ c = rotateLeft28(c, rotation);
2765
+ d = rotateLeft28(d, rotation);
2766
+ const combined = BigInt(c) << 28n | BigInt(d);
2767
+ roundKeys.push(permuteBits(combined, DES_PC2, 56));
2768
+ }
2769
+ return roundKeys;
2770
+ }
2771
+ function encryptDesBlock(block, roundKeys) {
2772
+ let value = permuteBits(bytesToBigInt(block), DES_INITIAL_PERMUTATION, 64);
2773
+ let left = Number(value >> 32n & 0xffffffffn);
2774
+ let right = Number(value & 0xffffffffn);
2775
+ for (const roundKey of roundKeys) {
2776
+ const nextLeft = right;
2777
+ const nextRight = (left ^ feistel(right, roundKey)) >>> 0;
2778
+ left = nextLeft >>> 0;
2779
+ right = nextRight;
2780
+ }
2781
+ value = BigInt(right) << 32n | BigInt(left);
2782
+ return bigIntToBytes(permuteBits(value, DES_FINAL_PERMUTATION, 64), 8);
2783
+ }
2784
+ function feistel(right, roundKey) {
2785
+ const expanded = permuteBits(BigInt(right >>> 0), DES_EXPANSION, 32) ^ roundKey;
2786
+ let output = 0;
2787
+ for (let index = 0; index < 8; index += 1) {
2788
+ const shift = BigInt((7 - index) * 6);
2789
+ const chunk = Number(expanded >> shift & 0x3fn);
2790
+ const row = (chunk & 32) >> 4 | chunk & 1;
2791
+ const column = chunk >> 1 & 15;
2792
+ output = output << 4 | DES_SBOXES[index][row * 16 + column];
2793
+ }
2794
+ return Number(permuteBits(BigInt(output >>> 0), DES_P_PERMUTATION, 32)) >>> 0;
2795
+ }
2796
+ function rotateLeft28(value, shift) {
2797
+ const masked = value & 268435455;
2798
+ return (masked << shift | masked >>> 28 - shift) & 268435455;
2799
+ }
2800
+ function permuteBits(input, table, inputBits) {
2801
+ let output = 0n;
2802
+ for (const position of table) {
2803
+ const shift = BigInt(inputBits - position);
2804
+ output = output << 1n | input >> shift & 1n;
2805
+ }
2806
+ return output;
2807
+ }
2808
+ function bytesToBigInt(bytes) {
2809
+ let value = 0n;
2810
+ for (const byte of bytes) {
2811
+ value = value << 8n | BigInt(byte);
2812
+ }
2813
+ return value;
2814
+ }
2815
+ function bigIntToBytes(value, length) {
2816
+ const out = Buffer.alloc(length);
2817
+ let remaining = value;
2818
+ for (let index = length - 1; index >= 0; index -= 1) {
2819
+ out[index] = Number(remaining & 0xffn);
2820
+ remaining >>= 8n;
2821
+ }
2822
+ return out;
2823
+ }
2824
+
434
2825
  // src/sse.ts
435
2826
  async function* parseSSEMessages(stream, signal) {
436
2827
  const reader = stream.getReader();
@@ -541,7 +2932,7 @@ function lifecyclePath(path2, isLocal, namespace) {
541
2932
  }
542
2933
 
543
2934
  // src/sandbox.ts
544
- var import_ws = __toESM(require("ws"), 1);
2935
+ var import_ws2 = __toESM(require("ws"), 1);
545
2936
  var PTY_OP_DATA = 0;
546
2937
  var PTY_OP_RESIZE = 1;
547
2938
  var PTY_OP_READY = 2;
@@ -585,7 +2976,7 @@ var Pty = class {
585
2976
  return () => this.exitHandlers.delete(handler);
586
2977
  }
587
2978
  async connect() {
588
- if (this.socket?.readyState === import_ws.default.OPEN) {
2979
+ if (this.socket?.readyState === import_ws2.default.OPEN) {
589
2980
  return this;
590
2981
  }
591
2982
  if (this.connectPromise) {
@@ -594,7 +2985,7 @@ var Pty = class {
594
2985
  this.intentionalDisconnect = false;
595
2986
  this.connectPromise = new Promise((resolve, reject) => {
596
2987
  let opened = false;
597
- const socket = new import_ws.default(this.wsUrl, {
2988
+ const socket = new import_ws2.default(this.wsUrl, {
598
2989
  headers: this.wsHeaders
599
2990
  });
600
2991
  this.socket = socket;
@@ -684,7 +3075,7 @@ var Pty = class {
684
3075
  await this.killSession();
685
3076
  }
686
3077
  requireOpenSocket() {
687
- if (!this.socket || this.socket.readyState !== import_ws.default.OPEN) {
3078
+ if (!this.socket || this.socket.readyState !== import_ws2.default.OPEN) {
688
3079
  throw new SandboxError("PTY is not connected");
689
3080
  }
690
3081
  return this.socket;
@@ -1010,6 +3401,26 @@ var Sandbox = class {
1010
3401
  await pty.connect();
1011
3402
  return pty;
1012
3403
  }
3404
+ async createTunnel(remotePort, options) {
3405
+ return TcpTunnel.listen({
3406
+ baseUrl: this.baseUrl,
3407
+ wsHeaders: this.wsHeaders,
3408
+ remotePort,
3409
+ localHost: options?.localHost,
3410
+ localPort: options?.localPort,
3411
+ connectTimeout: options?.connectTimeout
3412
+ });
3413
+ }
3414
+ async connectDesktop(options) {
3415
+ return Desktop.connect({
3416
+ baseUrl: this.baseUrl,
3417
+ wsHeaders: this.wsHeaders,
3418
+ port: options?.port,
3419
+ password: options?.password,
3420
+ shared: options?.shared,
3421
+ connectTimeout: options?.connectTimeout
3422
+ });
3423
+ }
1013
3424
  ptyWsUrl(sessionId, token) {
1014
3425
  let wsBase;
1015
3426
  if (this.baseUrl.startsWith("https://")) {
@@ -1209,10 +3620,12 @@ var SandboxClient = class _SandboxClient {
1209
3620
  return fromSnakeKeys(raw, "sandboxId");
1210
3621
  }
1211
3622
  // --- Snapshots ---
1212
- async snapshot(sandboxId) {
3623
+ async snapshot(sandboxId, options) {
3624
+ const requestOptions = options?.contentMode != null ? { body: { snapshot_content_mode: options.contentMode } } : void 0;
1213
3625
  const raw = await this.http.requestJson(
1214
3626
  "POST",
1215
- this.path(`sandboxes/${sandboxId}/snapshot`)
3627
+ this.path(`sandboxes/${sandboxId}/snapshot`),
3628
+ requestOptions
1216
3629
  );
1217
3630
  return fromSnakeKeys(raw, "snapshotId");
1218
3631
  }
@@ -1241,7 +3654,9 @@ var SandboxClient = class _SandboxClient {
1241
3654
  async snapshotAndWait(sandboxId, options) {
1242
3655
  const timeout = options?.timeout ?? 300;
1243
3656
  const pollInterval = options?.pollInterval ?? 1;
1244
- const result = await this.snapshot(sandboxId);
3657
+ const result = await this.snapshot(sandboxId, {
3658
+ contentMode: options?.contentMode
3659
+ });
1245
3660
  const deadline = Date.now() + timeout * 1e3;
1246
3661
  while (Date.now() < deadline) {
1247
3662
  const info = await this.getSnapshot(result.snapshotId);
@@ -2562,7 +4977,9 @@ async function createSandboxImage(source, options = {}, deps = {}) {
2562
4977
  });
2563
4978
  await executeDockerfilePlan(sandbox, plan, emit, sleep4);
2564
4979
  emit({ type: "status", message: "Creating snapshot..." });
2565
- const snapshot = await client.snapshotAndWait(sandbox.sandboxId);
4980
+ const snapshot = await client.snapshotAndWait(sandbox.sandboxId, {
4981
+ contentMode: "filesystem_only"
4982
+ });
2566
4983
  emit({
2567
4984
  type: "snapshot_created",
2568
4985
  snapshot_id: snapshot.snapshotId,
@@ -2607,6 +5024,7 @@ async function createSandboxImage(source, options = {}, deps = {}) {
2607
5024
  APIClient,
2608
5025
  CloudClient,
2609
5026
  ContainerState,
5027
+ Desktop,
2610
5028
  Image,
2611
5029
  ImageBuildOperationType,
2612
5030
  OutputMode,
@@ -2627,6 +5045,7 @@ async function createSandboxImage(source, options = {}, deps = {}) {
2627
5045
  SandboxStatus,
2628
5046
  SnapshotStatus,
2629
5047
  StdinMode,
5048
+ TcpTunnel,
2630
5049
  createSandboxImage,
2631
5050
  dockerfileContent
2632
5051
  });