cvmts: rate limit the 'sync' instruction

the original Guacamole code did this too I'm pretty sure, and it was even more aggressive about it.
This commit is contained in:
modeco80
2024-10-05 05:00:52 -04:00
parent 41ee71f053
commit 199924ff92

View File

@@ -30,6 +30,9 @@ const kCVMTSAssetsRoot = path.resolve(__dirname, '../../assets');
const kRestartTimeout = 5000; const kRestartTimeout = 5000;
// Rate in milliseconds that the 'sync' instruction should be sent to users.
const kSyncRateMs = 500;
type ChatHistory = { type ChatHistory = {
user: string; user: string;
msg: string; msg: string;
@@ -85,6 +88,8 @@ export default class CollabVMServer {
private ModPerms: number; private ModPerms: number;
private VM: VM; private VM: VM;
private lastSync: number = Date.now();
// Authentication manager // Authentication manager
private auth: AuthManager | null; private auth: AuthManager | null;
@@ -94,9 +99,6 @@ export default class CollabVMServer {
// Ban manager // Ban manager
private banmgr: BanManager; private banmgr: BanManager;
// queue of rects, reset every frame
private rectQueue: Rect[] = [];
private logger = pino({ name: 'CVMTS.Server' }); private logger = pino({ name: 'CVMTS.Server' });
constructor(config: IConfig, vm: VM, banmgr: BanManager, auth: AuthManager | null, geoipReader: ReaderModel | null) { constructor(config: IConfig, vm: VM, banmgr: BanManager, auth: AuthManager | null, geoipReader: ReaderModel | null) {
@@ -139,7 +141,6 @@ export default class CollabVMServer {
// add events // add events
self.VM.GetDisplay()?.on('resize', (size: Size) => self.OnDisplayResized(size)); self.VM.GetDisplay()?.on('resize', (size: Size) => self.OnDisplayResized(size));
self.VM.GetDisplay()?.on('rect', (rect: Rect) => self.OnDisplayRectangle(rect)); self.VM.GetDisplay()?.on('rect', (rect: Rect) => self.OnDisplayRectangle(rect));
self.VM.GetDisplay()?.on('frame', () => self.OnDisplayFrame());
} }
} }
@@ -849,20 +850,7 @@ export default class CollabVMServer {
} }
} }
private OnDisplayRectangle(rect: Rect) { private async OnDisplayRectangle(rect: Rect) {
this.rectQueue.push(rect);
}
private OnDisplayResized(size: Size) {
this.clients
.filter((c) => c.connectedToNode || c.viewMode == 1)
.forEach((c) => {
if (this.screenHidden && c.rank == Rank.Unregistered) return;
c.sendMsg(cvm.guacEncode('size', '0', size.width.toString(), size.height.toString()));
});
}
private async OnDisplayFrame() {
let self = this; let self = this;
let doRect = async (rect: Rect) => { let doRect = async (rect: Rect) => {
@@ -887,20 +875,35 @@ export default class CollabVMServer {
c.socket.sendBinary(encodedbin); c.socket.sendBinary(encodedbin);
} else { } else {
c.sendMsg(cvm.guacEncode('png', '0', '0', rect.x.toString(), rect.y.toString(), encodedb64)); c.sendMsg(cvm.guacEncode('png', '0', '0', rect.x.toString(), rect.y.toString(), encodedb64));
c.sendMsg(cvm.guacEncode('sync', Date.now().toString()));
} }
}); });
}; };
let promises: Promise<void>[] = []; await Promise.all([doRect(rect)]);
for (let rect of self.rectQueue) promises.push(doRect(rect)); // Send a sync if the time since the last sync has hit or went above the rate
// to send one
let syncDeltaMs = Date.now() - self.lastSync;
if (syncDeltaMs >= kSyncRateMs) {
let syncNow = Date.now();
self.clients
.filter((c) => c.connectedToNode || c.viewMode == 1)
.forEach((c) => {
if (self.screenHidden && c.rank == Rank.Unregistered) return;
c.sendMsg(cvm.guacEncode('sync', syncNow.toString()));
});
// javascript is a very solidly designed language with no holes self.lastSync = syncNow;
// or usability traps inside of it whatsoever }
this.rectQueue.length = 0; }
await Promise.all(promises); private OnDisplayResized(size: Size) {
this.clients
.filter((c) => c.connectedToNode || c.viewMode == 1)
.forEach((c) => {
if (this.screenHidden && c.rank == Rank.Unregistered) return;
c.sendMsg(cvm.guacEncode('size', '0', size.width.toString(), size.height.toString()));
});
} }
private async SendFullScreenWithSize(client: User) { private async SendFullScreenWithSize(client: User) {