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