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