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.
This commit is contained in:
UNITRONIX
2026-06-27 00:23:46 +02:00
parent c5c4c52e12
commit ad41260390
4 changed files with 55 additions and 10 deletions
+20 -2
View File
@@ -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<void>}
*/
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);
@@ -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');
}
+13 -5
View File
@@ -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'));
+12 -2
View File
@@ -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) });
});