deckide 3.5.35 → 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 };
|
|
@@ -248,6 +252,9 @@ export class AgentBrowserService {
|
|
|
248
252
|
// pushed to clients as binary WebSocket messages (no base64) to cut ~33% size
|
|
249
253
|
// and avoid per-frame string allocation.
|
|
250
254
|
lastFrameBuffer = null;
|
|
255
|
+
// Newest frame held back per slow client, flushed when its socket drains.
|
|
256
|
+
pendingFrames = new Map();
|
|
257
|
+
frameFlushTimer = null;
|
|
251
258
|
clients = new Set();
|
|
252
259
|
viewport = { ...DEFAULT_VIEWPORT };
|
|
253
260
|
pageUrl = null;
|
|
@@ -310,6 +317,11 @@ export class AgentBrowserService {
|
|
|
310
317
|
this.pageTitle = null;
|
|
311
318
|
this.screencastStarted = false;
|
|
312
319
|
this.lastFrameBuffer = null;
|
|
320
|
+
this.pendingFrames.clear();
|
|
321
|
+
if (this.frameFlushTimer) {
|
|
322
|
+
clearInterval(this.frameFlushTimer);
|
|
323
|
+
this.frameFlushTimer = null;
|
|
324
|
+
}
|
|
313
325
|
if (proc && proc.exitCode == null && proc.signalCode == null) {
|
|
314
326
|
await new Promise((resolve) => {
|
|
315
327
|
const killTimer = setTimeout(() => {
|
|
@@ -347,6 +359,7 @@ export class AgentBrowserService {
|
|
|
347
359
|
});
|
|
348
360
|
socket.on('close', () => {
|
|
349
361
|
this.clients.delete(socket);
|
|
362
|
+
this.pendingFrames.delete(socket);
|
|
350
363
|
if (this.clients.size === 0) {
|
|
351
364
|
void this.stopScreencast();
|
|
352
365
|
}
|
|
@@ -449,7 +462,7 @@ export class AgentBrowserService {
|
|
|
449
462
|
// change, so without this a client attaching to a static page would stay
|
|
450
463
|
// blank until the page next repaints.
|
|
451
464
|
if (this.lastFrameBuffer) {
|
|
452
|
-
this.
|
|
465
|
+
this.writeFrame(socket, this.lastFrameBuffer);
|
|
453
466
|
}
|
|
454
467
|
if (this.clients.size > 0) {
|
|
455
468
|
await this.startScreencast();
|
|
@@ -732,14 +745,56 @@ export class AgentBrowserService {
|
|
|
732
745
|
}
|
|
733
746
|
broadcastBinary(buffer) {
|
|
734
747
|
for (const socket of this.clients) {
|
|
735
|
-
this.
|
|
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;
|
|
736
791
|
}
|
|
737
792
|
}
|
|
738
|
-
|
|
793
|
+
writeFrame(socket, buffer) {
|
|
739
794
|
if (!isOpen(socket)) {
|
|
740
795
|
return;
|
|
741
796
|
}
|
|
742
|
-
//
|
|
797
|
+
// Hard ceiling: a client this far behind is unrecoverable, so drop it.
|
|
743
798
|
if (socket.bufferedAmount > MAX_CLIENT_BUFFERED_AMOUNT) {
|
|
744
799
|
try {
|
|
745
800
|
socket.close(1009, 'Browser stream overflow');
|