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