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