From ad41260390ecde3ce5242b25d5cec8635ffdbc14 Mon Sep 17 00:00:00 2001 From: UNITRONIX <36471318+UNITRONIX@users.noreply.github.com> Date: Sat, 27 Jun 2026 00:23:46 +0200 Subject: [PATCH] feat(web-remote): improve file transfer connection handling Enhance the file transfer connection logic by adding an `isConnected` method to check the readiness of the file connection. Update the `ensureFileConnection` method to handle connection errors more gracefully and ensure proper disconnection. Modify the file transfer modal to improve user feedback during connection states and streamline the rendering of remote entries. Additionally, refine the file transfer class to better manage connection requirements. --- web-nodejs/public/js/rdclient/client.js | 22 +++++++++++++++++-- .../public/js/rdclient/file-connection.js | 11 +++++++++- web-nodejs/public/js/rdclient/file-modal.js | 18 ++++++++++----- web-nodejs/public/js/rdclient/filetransfer.js | 14 ++++++++++-- 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/web-nodejs/public/js/rdclient/client.js b/web-nodejs/public/js/rdclient/client.js index e2f0439a..c2c46874 100644 --- a/web-nodejs/public/js/rdclient/client.js +++ b/web-nodejs/public/js/rdclient/client.js @@ -46,7 +46,8 @@ class RDClient { proto: this.proto, sendMessage: (msg) => this._sendFileTransferMessage(msg), emit: (event, ...args) => this._emit(event, ...args), - ensureConnected: () => this.ensureFileConnection() + ensureConnected: () => this.ensureFileConnection(), + isConnected: () => this.isFileConnectionReady() }); // State @@ -254,6 +255,10 @@ class RDClient { * Open dedicated FILE_TRANSFER relay (lazy). Reuses desktop session password. * @returns {Promise} */ + isFileConnectionReady() { + return !!(this._fileConnection && this._fileConnection.state === 'ready'); + } + async ensureFileConnection() { if (typeof RDFileConnection !== 'function') { throw new Error('File transfer module not loaded'); @@ -272,9 +277,19 @@ class RDClient { this._fileConnection.on('2fa_required', () => this._emit('2fa_required')); this._fileConnection.on('2fa_error', (err) => this._emit('2fa_error', err)); this._fileConnection.on('login_error', (err) => this._emit('login_error', err)); + this._fileConnection.on('disconnected', () => { + if (this._fileConnection && this._fileConnection.state !== 'ready') { + this._fileConnection = null; + } + }); } if (this._fileConnection.state === 'ready') return; - await this._fileConnection.connect(this._sessionPassword || ''); + try { + await this._fileConnection.connect(this._sessionPassword || ''); + } catch (err) { + this.disconnectFileConnection(); + throw err; + } } _sendFileTransferMessage(msgObj) { @@ -1103,6 +1118,9 @@ class RDClient { // Enable file transfer this.fileTransfer.enable(); + this.ensureFileConnection().catch(function (err) { + console.warn('[RDClient] File transfer preconnect:', err.message || err); + }); // Initialize video decoder callbacks this.video.onFrame = (frame) => this.renderer.pushFrame(frame); diff --git a/web-nodejs/public/js/rdclient/file-connection.js b/web-nodejs/public/js/rdclient/file-connection.js index 2039ec05..23b20157 100644 --- a/web-nodejs/public/js/rdclient/file-connection.js +++ b/web-nodejs/public/js/rdclient/file-connection.js @@ -99,6 +99,11 @@ class RDFileConnection { async _doConnect(password) { try { + if (this.conn) { + try { this.conn.close(); } catch (_e) { /* ignore */ } + } + this.conn = new RDConnection(); + this.crypto = new RDCrypto(); this._setState('connecting'); this._emit('log', 'Opening file transfer session…'); @@ -110,6 +115,9 @@ class RDFileConnection { this._keyExchangePending = false; this._keyExchangeDone = false; this._peerEncryptionConfirmed = false; + this._loginChallenge = ''; + this._loginSalt = ''; + this._pendingPassword = password; const ct = this._connType(); await this.conn.connectRendezvous(); @@ -230,7 +238,8 @@ class RDFileConnection { if (this._loginReject) { this._loginReject(new Error('Disconnected')); } - this.conn.close(); + this._connectPromise = null; + try { this.conn.close(); } catch (_e) { /* ignore */ } this._setState('disconnected'); } diff --git a/web-nodejs/public/js/rdclient/file-modal.js b/web-nodejs/public/js/rdclient/file-modal.js index f688dfb9..dcd1ec1b 100644 --- a/web-nodejs/public/js/rdclient/file-modal.js +++ b/web-nodejs/public/js/rdclient/file-modal.js @@ -186,6 +186,11 @@ t('remote.file_transfer', 'File Transfer') + ' — ' + (session.deviceName || session.deviceId); this._updateLocalHint(); this._updateLocalToolbarState(); + this._renderLocalList(); + this._remoteEntries = []; + this._selectedRemote = null; + this._el.querySelector('.ft-remote-path').textContent = ''; + this._renderRemoteLoading(t('remote.file_connecting', 'Connecting file transfer session…')); this._el.style.display = 'flex'; document.getElementById('btn-file-transfer')?.classList.add('active'); if (this._wiredSessionId !== session.deviceId) { @@ -204,9 +209,6 @@ if (this._session && this._session.client && this._session.client.fileTransfer) { this._session.client.fileTransfer._saveDownload = null; } - if (this._session && this._session.client && this._session.client.disconnectFileConnection) { - this._session.client.disconnectFileConnection(); - } }; FileTransferModal.prototype.isOpen = function () { @@ -216,9 +218,15 @@ FileTransferModal.prototype._wireClient = function (session) { var self = this; var client = session.client; - client.on('file_browsing', function () { self._renderRemoteLoading(); }); + client.on('file_browsing', function () { + if (!self._remoteEntries.length) { + self._renderRemoteLoading(t('remote.file_loading', 'Loading…')); + } + }); client.on('file_connecting', function () { - self._renderRemoteLoading(t('remote.file_connecting', 'Connecting file transfer session…')); + if (!self._remoteEntries.length) { + self._renderRemoteLoading(t('remote.file_connecting', 'Connecting file transfer session…')); + } }); client.on('file_connect_error', function (data) { self._renderRemoteError(data.error || t('remote.file_connect_failed', 'Could not open file transfer session')); diff --git a/web-nodejs/public/js/rdclient/filetransfer.js b/web-nodejs/public/js/rdclient/filetransfer.js index 884b677c..67e46ef0 100644 --- a/web-nodejs/public/js/rdclient/filetransfer.js +++ b/web-nodejs/public/js/rdclient/filetransfer.js @@ -19,12 +19,14 @@ class RDFileTransfer { * @param {Function} opts.sendMessage - Function to send peer message: (msgObj) => void * @param {Function} opts.emit - Event emitter: (event, ...args) => void * @param {Function} [opts.ensureConnected] - Async hook before browse/upload + * @param {Function} [opts.isConnected] - Returns true when file relay is ready */ constructor(opts) { this._proto = opts.proto; this._sendMessage = opts.sendMessage; this._emit = opts.emit; this._ensureConnected = opts.ensureConnected || null; + this._isConnected = opts.isConnected || null; /** @type {string} Current remote directory path */ this._currentPath = ''; @@ -95,10 +97,16 @@ class RDFileTransfer { }); } + _needsFileConnection() { + return this._ensureConnected && (!this._isConnected || !this._isConnected()); + } + _runWithConnection(run) { const self = this; if (this._ensureConnected) { - this._emit('file_connecting'); + if (this._needsFileConnection()) { + this._emit('file_connecting'); + } return this._ensureConnected().then(function () { run(); }).catch(function (err) { @@ -188,7 +196,9 @@ class RDFileTransfer { }, 5000); }; if (this._ensureConnected) { - this._emit('file_connecting'); + if (this._needsFileConnection()) { + this._emit('file_connecting'); + } this._ensureConnected().then(run).catch(function (err) { self._emit('file_connect_error', { error: err.message || String(err) }); });