fix(gateway): keep view-only desktop sessions connected [AI-assisted] (#121256)

* fix(gateway): keep view-only desktop sessions connected

[AI-assisted]

* fix(gateway): satisfy RFB filter type checks

[AI-assisted]
This commit is contained in:
Peter Steinberger
2026-08-09 15:33:50 -07:00
committed by GitHub
parent ce53f7e82e
commit e9cf5dde56
2 changed files with 69 additions and 1 deletions
@@ -89,6 +89,63 @@ describe("RFB view-only client message filter", () => {
});
});
it("forwards the fragmented noVNC 1.7 display and flow-control sequence", () => {
const filter = enterMessagePhase();
const setPixelFormat = Buffer.alloc(20);
const setEncodings = Buffer.alloc(100);
setEncodings[0] = 2;
setEncodings.writeUInt16BE(24, 2);
const framebufferUpdateRequest = Buffer.from([3, 1, 0, 0, 0, 0, 0, 64, 0, 64]);
const clientFence = Buffer.from([248, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
const enableContinuousUpdates = Buffer.from([150, 1, 0, 0, 0, 0, 0, 64, 0, 64]);
const cutText = Buffer.concat([Buffer.from([6, 0, 0, 0, 0, 0, 0, 8]), Buffer.alloc(8)]);
const capturedSequence = Buffer.concat([
setPixelFormat,
setEncodings,
framebufferUpdateRequest,
clientFence,
enableContinuousUpdates,
cutText,
enableContinuousUpdates,
]);
const forwarded: Buffer[] = [];
for (const byte of capturedSequence) {
const result = filter.filter(Buffer.of(byte));
if ("error" in result) {
throw new Error(result.error);
}
forwarded.push(result.forward);
}
expect(Buffer.concat(forwarded)).toEqual(
Buffer.concat([
setPixelFormat,
setEncodings,
framebufferUpdateRequest,
clientFence,
enableContinuousUpdates,
enableContinuousUpdates,
]),
);
});
it("uses the ClientFence payload length to preserve message boundaries", () => {
const filter = enterMessagePhase();
const clientFence = Buffer.concat([
Buffer.from([248, 0, 0, 0, 0, 0, 0, 0, 3]),
Buffer.from("abc"),
]);
const framebufferUpdateRequest = Buffer.from([3, 1, 0, 0, 0, 0, 0, 64, 0, 64]);
expect(filter.filter(clientFence.subarray(0, 10))).toEqual({ forward: Buffer.alloc(0) });
expect(
filter.filter(Buffer.concat([clientFence.subarray(10), framebufferUpdateRequest])),
).toEqual({
forward: Buffer.concat([clientFence, framebufferUpdateRequest]),
});
});
it("reassembles a message split across three chunks", () => {
const filter = enterMessagePhase();
const completePrefix = Buffer.from([3, 1, 0, 0, 0, 0, 0, 64, 0, 64]);
@@ -45,6 +45,11 @@ export function createRfbClientMessageFilter() {
return 6;
case 6:
return pending.length < 8 ? 8 : 8 + pending.readUInt32BE(4);
case 150:
return 10;
case 248:
// ClientFence's payload length byte follows its 8-byte fixed header.
return pending.length < 9 ? 9 : 9 + pending.readUInt8(8);
default:
return `unsupported RFB client message type ${pending[0]}`;
}
@@ -75,7 +80,13 @@ export function createRfbClientMessageFilter() {
pending[0] = 1;
forwarded.push(pending);
phase = "messages";
} else if (pending[0] === 0 || pending[0] === 2 || pending[0] === 3) {
} else if (
pending[0] === 0 ||
pending[0] === 2 ||
pending[0] === 3 ||
pending[0] === 150 ||
pending[0] === 248
) {
forwarded.push(pending);
}
pending = Buffer.alloc(0);