deckide 3.5.34 → 3.5.36
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.
|
@@ -10,6 +10,10 @@ const DEFAULT_PROFILE_DIR = path.join(os.homedir(), '.local', 'share', 'agent-br
|
|
|
10
10
|
const DEFAULT_OUTPUT_DIR = path.join(os.homedir(), '.local', 'share', 'agent-browser', 'output');
|
|
11
11
|
const START_TIMEOUT_MS = 30_000;
|
|
12
12
|
const MAX_CLIENT_BUFFERED_AMOUNT = 4 * 1024 * 1024;
|
|
13
|
+
// Above this many bytes already queued on a client socket we hold the newest
|
|
14
|
+
// frame instead of piling on, then flush it once the socket drains.
|
|
15
|
+
const FRAME_COALESCE_THRESHOLD = 256 * 1024;
|
|
16
|
+
const FRAME_FLUSH_INTERVAL_MS = 16;
|
|
13
17
|
const DEFAULT_VIEWPORT = { width: 1280, height: 720 };
|
|
14
18
|
const MIN_VIEWPORT = { width: 320, height: 240 };
|
|
15
19
|
const MAX_VIEWPORT = { width: 3840, height: 2160 };
|
|
@@ -243,6 +247,14 @@ export class AgentBrowserService {
|
|
|
243
247
|
cdpSocket = null;
|
|
244
248
|
cdpConnecting = null;
|
|
245
249
|
screencastStarted = false;
|
|
250
|
+
// Raw JPEG bytes of the most recent screencast frame, kept so reconnecting or
|
|
251
|
+
// late-joining clients can be shown the current page immediately. Frames are
|
|
252
|
+
// pushed to clients as binary WebSocket messages (no base64) to cut ~33% size
|
|
253
|
+
// and avoid per-frame string allocation.
|
|
254
|
+
lastFrameBuffer = null;
|
|
255
|
+
// Newest frame held back per slow client, flushed when its socket drains.
|
|
256
|
+
pendingFrames = new Map();
|
|
257
|
+
frameFlushTimer = null;
|
|
246
258
|
clients = new Set();
|
|
247
259
|
viewport = { ...DEFAULT_VIEWPORT };
|
|
248
260
|
pageUrl = null;
|
|
@@ -304,6 +316,12 @@ export class AgentBrowserService {
|
|
|
304
316
|
this.pageUrl = null;
|
|
305
317
|
this.pageTitle = null;
|
|
306
318
|
this.screencastStarted = false;
|
|
319
|
+
this.lastFrameBuffer = null;
|
|
320
|
+
this.pendingFrames.clear();
|
|
321
|
+
if (this.frameFlushTimer) {
|
|
322
|
+
clearInterval(this.frameFlushTimer);
|
|
323
|
+
this.frameFlushTimer = null;
|
|
324
|
+
}
|
|
307
325
|
if (proc && proc.exitCode == null && proc.signalCode == null) {
|
|
308
326
|
await new Promise((resolve) => {
|
|
309
327
|
const killTimer = setTimeout(() => {
|
|
@@ -341,6 +359,7 @@ export class AgentBrowserService {
|
|
|
341
359
|
});
|
|
342
360
|
socket.on('close', () => {
|
|
343
361
|
this.clients.delete(socket);
|
|
362
|
+
this.pendingFrames.delete(socket);
|
|
344
363
|
if (this.clients.size === 0) {
|
|
345
364
|
void this.stopScreencast();
|
|
346
365
|
}
|
|
@@ -438,6 +457,13 @@ export class AgentBrowserService {
|
|
|
438
457
|
await this.start();
|
|
439
458
|
await this.ensureCdp();
|
|
440
459
|
await this.sendStatus(socket);
|
|
460
|
+
// Replay the most recent frame so a reconnecting or late-joining client
|
|
461
|
+
// sees the current page immediately. Screencast only emits frames on
|
|
462
|
+
// change, so without this a client attaching to a static page would stay
|
|
463
|
+
// blank until the page next repaints.
|
|
464
|
+
if (this.lastFrameBuffer) {
|
|
465
|
+
this.writeFrame(socket, this.lastFrameBuffer);
|
|
466
|
+
}
|
|
441
467
|
if (this.clients.size > 0) {
|
|
442
468
|
await this.startScreencast();
|
|
443
469
|
}
|
|
@@ -660,11 +686,9 @@ export class AgentBrowserService {
|
|
|
660
686
|
void this.cdp?.send('Page.screencastFrameAck', { sessionId }).catch(() => undefined);
|
|
661
687
|
}
|
|
662
688
|
if (data) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
metadata: params.metadata ?? null,
|
|
667
|
-
});
|
|
689
|
+
const buffer = Buffer.from(data, 'base64');
|
|
690
|
+
this.lastFrameBuffer = buffer;
|
|
691
|
+
this.broadcastBinary(buffer);
|
|
668
692
|
}
|
|
669
693
|
return;
|
|
670
694
|
}
|
|
@@ -719,6 +743,79 @@ export class AgentBrowserService {
|
|
|
719
743
|
send(socket, payload) {
|
|
720
744
|
this.sendRaw(socket, JSON.stringify(payload));
|
|
721
745
|
}
|
|
746
|
+
broadcastBinary(buffer) {
|
|
747
|
+
for (const socket of this.clients) {
|
|
748
|
+
this.queueFrameForClient(socket, buffer);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
// Per-client, backpressure-aware frame delivery. A fast client (e.g. on
|
|
752
|
+
// localhost) drains instantly and receives every frame; a slow client (remote
|
|
753
|
+
// or mobile) has only the *newest* frame held and gets it once its send buffer
|
|
754
|
+
// clears. This trades frame rate for latency so motion stays smooth instead of
|
|
755
|
+
// accumulating a backlog that stutters and then bursts.
|
|
756
|
+
queueFrameForClient(socket, buffer) {
|
|
757
|
+
if (!isOpen(socket)) {
|
|
758
|
+
this.pendingFrames.delete(socket);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (socket.bufferedAmount > FRAME_COALESCE_THRESHOLD) {
|
|
762
|
+
this.pendingFrames.set(socket, buffer);
|
|
763
|
+
this.ensureFrameFlushTimer();
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
this.pendingFrames.delete(socket);
|
|
767
|
+
this.writeFrame(socket, buffer);
|
|
768
|
+
}
|
|
769
|
+
ensureFrameFlushTimer() {
|
|
770
|
+
if (this.frameFlushTimer) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
this.frameFlushTimer = setInterval(() => this.flushPendingFrames(), FRAME_FLUSH_INTERVAL_MS);
|
|
774
|
+
this.frameFlushTimer.unref?.();
|
|
775
|
+
}
|
|
776
|
+
flushPendingFrames() {
|
|
777
|
+
for (const [socket, buffer] of this.pendingFrames) {
|
|
778
|
+
if (!isOpen(socket)) {
|
|
779
|
+
this.pendingFrames.delete(socket);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (socket.bufferedAmount > FRAME_COALESCE_THRESHOLD) {
|
|
783
|
+
continue; // still draining; keep holding the newest frame
|
|
784
|
+
}
|
|
785
|
+
this.pendingFrames.delete(socket);
|
|
786
|
+
this.writeFrame(socket, buffer);
|
|
787
|
+
}
|
|
788
|
+
if (this.pendingFrames.size === 0 && this.frameFlushTimer) {
|
|
789
|
+
clearInterval(this.frameFlushTimer);
|
|
790
|
+
this.frameFlushTimer = null;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
writeFrame(socket, buffer) {
|
|
794
|
+
if (!isOpen(socket)) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
// Hard ceiling: a client this far behind is unrecoverable, so drop it.
|
|
798
|
+
if (socket.bufferedAmount > MAX_CLIENT_BUFFERED_AMOUNT) {
|
|
799
|
+
try {
|
|
800
|
+
socket.close(1009, 'Browser stream overflow');
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
// ignore
|
|
804
|
+
}
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
try {
|
|
808
|
+
socket.send(buffer, { binary: true });
|
|
809
|
+
}
|
|
810
|
+
catch {
|
|
811
|
+
try {
|
|
812
|
+
socket.close(1011, 'Browser stream send failed');
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
// ignore
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
722
819
|
sendRaw(socket, payload) {
|
|
723
820
|
if (!isOpen(socket)) {
|
|
724
821
|
return;
|