From 021f296a7eaacd81b511d8f8ae8eadc11585014a Mon Sep 17 00:00:00 2001 From: skolarianer <6286711+skolarianer@users.noreply.github.com> Date: Tue, 20 Oct 2026 05:40:48 +0200 Subject: [PATCH] Acknowledge browser file streams --- static/client.html | 4 ++++ tests/client-file-stream-ack.mjs | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/client-file-stream-ack.mjs diff --git a/static/client.html b/static/client.html index 7382c44..01a6481 100644 --- a/static/client.html +++ b/static/client.html @@ -1491,6 +1491,9 @@ // ── File download handler (server-initiated file transfer) ── client.onfile = function(stream, mimetype, filename) { var reader = new Guacamole.BlobReader(stream, mimetype); + reader.onprogress = function(length) { + stream.sendAck('Received', Guacamole.Status.Code.SUCCESS); + }; reader.onend = function() { var blob = reader.getBlob(); var a = document.createElement('a'); @@ -1502,6 +1505,7 @@ setTimeout(function() { URL.revokeObjectURL(a.href); }, 1000); setFileStatus('Received file: ' + filename); }; + stream.sendAck('OK', Guacamole.Status.Code.SUCCESS); }; function tracing_log(msg) { diff --git a/tests/client-file-stream-ack.mjs b/tests/client-file-stream-ack.mjs new file mode 100644 index 0000000..b3d125f --- /dev/null +++ b/tests/client-file-stream-ack.mjs @@ -0,0 +1,41 @@ +import { readFile } from 'node:fs/promises'; +import { dirname, resolve, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import vm from 'node:vm'; +import assert from 'node:assert/strict'; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const html = await readFile(join(root, 'static/client.html'), 'utf8'); +const marker = 'client.onfile = function(stream, mimetype, filename) {'; +const start = html.indexOf(marker); +assert.notEqual(start, -1, 'client.onfile handler must exist'); + +const end = html.indexOf('function tracing_log(msg) {', start); +assert.ok(end > start, 'client onfile handler must be present'); + +let reader; +const acknowledgements = []; +const client = {}; +const ctx = { + client, + Guacamole: { + BlobReader: class { + constructor() { reader = this; } + }, + Status: { Code: { SUCCESS: 0 } } + } +}; +vm.createContext(ctx); +vm.runInContext(html.slice(start, end), ctx); + +const stream = { + sendAck(message, code) { acknowledgements.push({ message, code }); } +}; +client.onfile(stream, 'text/plain', 'download.txt'); +assert.deepEqual(acknowledgements, [ + { message: 'OK', code: 0 } +], 'the stream should be accepted once the reader is ready'); + +reader.onprogress(); +assert.deepEqual(acknowledgements.at(-1), { message: 'Received', code: 0 }); +console.log('client-file-stream-ack: ok');