diff --git a/package-lock.json b/package-lock.json index c901d10..434baf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "electerm-web", - "version": "2.51.3", + "version": "2.51.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "electerm-web", - "version": "2.51.3", + "version": "2.51.8", "hasInstallScript": true, "license": "MIT", "dependencies": { "@electerm/electerm-locales": "2.0.11", "@electerm/electerm-themes": "^1.0.1", "@electerm/rdpjs": "^1.0.0", + "@electerm/ssh2": "1.16.0", "@yetzt/nedb": "1.8.0", "axios": "^1.7.7", "dayjs": "^1.11.13", @@ -44,14 +45,13 @@ "socks-proxy-agent": "8.0.1", "socksv5": "^0.0.6", "ssh-config": "5.0.1", - "ssh2": "1.16.0", "strip-ansi": "^7.1.0", "stylus": "0.64.0", "tar": "^7.4.3" }, "devDependencies": { "@ant-design/icons": "5.5.1", - "@electerm/electerm-react": "^1.51.3", + "@electerm/electerm-react": "^1.51.8", "@electerm/electerm-resource": "1.3.7", "@electerm/strip-ansi": "^1.0.0", "@novnc/novnc": "^1.4.0", @@ -515,9 +515,9 @@ "integrity": "sha512-rqklJTAlvzHBs2kOy6JVNpPa3QIUPs138HqgXCbvjY8R9VrNWoAYIx+2gUg1vLCVrmg6kVXqOrhxsfhxT34YuQ==" }, "node_modules/@electerm/electerm-react": { - "version": "1.51.3", - "resolved": "https://registry.npmmirror.com/@electerm/electerm-react/-/electerm-react-1.51.3.tgz", - "integrity": "sha512-SPN7LIf8IHiPlRQBrEamdp+QhrF8ggyLtLmnRF3jM3W9N/W2z/T9+/VpO4i6OkDyKl/1PPz0RSGUjbD5apS+QA==", + "version": "1.51.8", + "resolved": "https://registry.npmmirror.com/@electerm/electerm-react/-/electerm-react-1.51.8.tgz", + "integrity": "sha512-2Uh8G8E7/DkcA8CO2hT6s5vAFeUDf7NTP4qAeu7/Gnmk1uxUgfhM4nvtg5MKEPBFlSjFz98VrMJT9qciKJ6oNQ==", "dev": true, "engines": { "node": ">=18.0.0" @@ -545,6 +545,23 @@ "node-forge": "^1.3.1" } }, + "node_modules/@electerm/ssh2": { + "version": "1.16.0", + "resolved": "https://registry.npmmirror.com/@electerm/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-cBr6tqD1l4C+NC15qBLalZM68Rt60rs25yLGeFJ/u7SOWva6tdY/TbTsWbO4S2XmIhG321hMpYgm/UFSWPo4UA==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.20.0" + } + }, "node_modules/@electerm/strip-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@electerm/strip-ansi/-/strip-ansi-1.0.0.tgz", @@ -8236,23 +8253,6 @@ "resolved": "https://registry.npmmirror.com/ssh-config/-/ssh-config-5.0.1.tgz", "integrity": "sha512-Bh9CRGFq7pLpWFPmLOyirzYhbpme8FXZe3lZckWvmABdcIEiGB8tNbmEEZdppnr6EiQ0WcGTMoYDp8Tjomq9gw==" }, - "node_modules/ssh2": { - "version": "1.16.0", - "resolved": "https://registry.npmmirror.com/ssh2/-/ssh2-1.16.0.tgz", - "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", - "hasInstallScript": true, - "dependencies": { - "asn1": "^0.2.6", - "bcrypt-pbkdf": "^1.0.2" - }, - "engines": { - "node": ">=10.16.0" - }, - "optionalDependencies": { - "cpu-features": "~0.0.10", - "nan": "^2.20.0" - } - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -9813,9 +9813,9 @@ "integrity": "sha512-rqklJTAlvzHBs2kOy6JVNpPa3QIUPs138HqgXCbvjY8R9VrNWoAYIx+2gUg1vLCVrmg6kVXqOrhxsfhxT34YuQ==" }, "@electerm/electerm-react": { - "version": "1.51.3", - "resolved": "https://registry.npmmirror.com/@electerm/electerm-react/-/electerm-react-1.51.3.tgz", - "integrity": "sha512-SPN7LIf8IHiPlRQBrEamdp+QhrF8ggyLtLmnRF3jM3W9N/W2z/T9+/VpO4i6OkDyKl/1PPz0RSGUjbD5apS+QA==", + "version": "1.51.8", + "resolved": "https://registry.npmmirror.com/@electerm/electerm-react/-/electerm-react-1.51.8.tgz", + "integrity": "sha512-2Uh8G8E7/DkcA8CO2hT6s5vAFeUDf7NTP4qAeu7/Gnmk1uxUgfhM4nvtg5MKEPBFlSjFz98VrMJT9qciKJ6oNQ==", "dev": true }, "@electerm/electerm-resource": { @@ -9837,6 +9837,17 @@ "node-forge": "^1.3.1" } }, + "@electerm/ssh2": { + "version": "1.16.0", + "resolved": "https://registry.npmmirror.com/@electerm/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-cBr6tqD1l4C+NC15qBLalZM68Rt60rs25yLGeFJ/u7SOWva6tdY/TbTsWbO4S2XmIhG321hMpYgm/UFSWPo4UA==", + "requires": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2", + "cpu-features": "~0.0.10", + "nan": "^2.20.0" + } + }, "@electerm/strip-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@electerm/strip-ansi/-/strip-ansi-1.0.0.tgz", @@ -15355,17 +15366,6 @@ "resolved": "https://registry.npmmirror.com/ssh-config/-/ssh-config-5.0.1.tgz", "integrity": "sha512-Bh9CRGFq7pLpWFPmLOyirzYhbpme8FXZe3lZckWvmABdcIEiGB8tNbmEEZdppnr6EiQ0WcGTMoYDp8Tjomq9gw==" }, - "ssh2": { - "version": "1.16.0", - "resolved": "https://registry.npmmirror.com/ssh2/-/ssh2-1.16.0.tgz", - "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", - "requires": { - "asn1": "^0.2.6", - "bcrypt-pbkdf": "^1.0.2", - "cpu-features": "~0.0.10", - "nan": "^2.20.0" - } - }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", diff --git a/package.json b/package.json index 73ac2fd..be19132 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electerm-web", - "version": "2.51.3", + "version": "2.51.8", "description": "Running electerm in as web app", "main": "src/app/app.js", "type": "module", @@ -47,7 +47,7 @@ "preferGlobal": true, "devDependencies": { "@ant-design/icons": "5.5.1", - "@electerm/electerm-react": "^1.51.3", + "@electerm/electerm-react": "^1.51.8", "@electerm/electerm-resource": "1.3.7", "@electerm/strip-ansi": "^1.0.0", "@novnc/novnc": "^1.4.0", @@ -92,6 +92,7 @@ "@electerm/electerm-locales": "2.0.11", "@electerm/electerm-themes": "^1.0.1", "@electerm/rdpjs": "^1.0.0", + "@electerm/ssh2": "1.16.0", "@yetzt/nedb": "1.8.0", "axios": "^1.7.7", "dayjs": "^1.11.13", @@ -123,7 +124,6 @@ "socks-proxy-agent": "8.0.1", "socksv5": "^0.0.6", "ssh-config": "5.0.1", - "ssh2": "1.16.0", "strip-ansi": "^7.1.0", "stylus": "0.64.0", "tar": "^7.4.3" diff --git a/src/app/server/session-sftp.js b/src/app/server/session-sftp.js index 2b9f6dc..9f6f0f8 100644 --- a/src/app/server/session-sftp.js +++ b/src/app/server/session-sftp.js @@ -57,25 +57,21 @@ class SftpBase extends TerminalBase { getHomeDir () { // return this.runCmd('eval echo "~$different_user"') // ext_home_dir - return this.getSftpHomeDir() - .catch(err => { - console.error('get home dir error', err) - return this.realpath('') - }) + return this.realpath('') } - getSftpHomeDir () { - // return this.runCmd('eval echo "~$different_user"') - // ext_home_dir - return new Promise((resolve, reject) => { - this.sftp.ext_home_dir('', (err, path) => { - if (err) { - return reject(err) - } - resolve(path) - }) - }) - } + // getSftpHomeDir () { + // // return this.runCmd('eval echo "~$different_user"') + // // ext_home_dir + // return new Promise((resolve, reject) => { + // this.sftp.ext_home_dir('', (err, path) => { + // if (err) { + // return reject(err) + // } + // resolve(path) + // }) + // }) + // } /** * rmdir diff --git a/src/app/server/session-ssh.js b/src/app/server/session-ssh.js index e584c61..2c84498 100644 --- a/src/app/server/session-ssh.js +++ b/src/app/server/session-ssh.js @@ -1,7 +1,7 @@ /** * terminal/sftp/serial class */ -import { Client } from 'ssh2' +import { Client } from '@electerm/ssh2' import proxySock from './socks.js' import _ from 'lodash' import uid from '../common/uid.js' diff --git a/src/app/server/transfer.js b/src/app/server/transfer.js index e4538aa..de89bf9 100644 --- a/src/app/server/transfer.js +++ b/src/app/server/transfer.js @@ -6,14 +6,6 @@ import fs from 'fs' import _ from 'lodash' import log from '../common/log.js' -function tryCreateBuffer (size) { - try { - return Buffer.allocUnsafe(size) - } catch (ex) { - return ex - } -} - export class Transfer { constructor ({ remotePath, @@ -35,227 +27,225 @@ export class Transfer { this.srcPath = isd ? remotePath : localPath this.dstPath = !isd ? remotePath : localPath this.pausing = false - + this.hadError = false + this.isUpload = isd + this.options = options + this.concurrency = options.concurrency || 64 + this.chunkSize = options.chunkSize || 32768 + this.mode = options.mode this.onData = _.throttle((count) => { ws.s({ id: 'transfer:data:' + id, data: count }) }, 3000) + this.timers = {} this.ws = ws this.fastXfer(options, type) } + tryCreateBuffer = (size) => { + try { + return Buffer.allocUnsafe(size) + } catch (ex) { + return ex + } + } + // from https://github.com/mscdex/ssh2-streams/blob/master/lib/sftp.js - fastXfer = (opts, type) => { + fastXfer = () => { + const { src, srcPath } = this + src.open(srcPath, 'r', this.onSrcOpen) + } + + onSrcOpen = (err, sourceHandle) => { + if (err) { + return this.onError(err) + } + if (this.onDestroy) { + return + } + const { src } = this + const th = this + + th.srcHandle = sourceHandle + + src.fstat(th.srcHandle, this.tryStat) + } + + tryStat = (err, attrs) => { + const { src, dst, srcPath, dstPath } = this + const th = this + if (err) { + if (src !== fs) { + // Try stat() for sftp servers that may not support fstat() for + // whatever reason + src.stat(srcPath, (err_, attrs_) => { + if (err_) { + return th.onError(err_) + } + this.tryStat(null, attrs_) + }) + return + } + return th.onError(err) + } + this.fsize = attrs.size + dst.open(dstPath, 'w', this.onDstOpen) + } + + onDstOpen = (err, destHandle) => { + if (err) { + return this.onError(err) + } + + if (this.onDestroy) { + return + } + let { - concurrency = 64, - chunkSize = 32768, + concurrency, + chunkSize, mode - } = opts + } = this const onstep = this.onData - const { src, dst, srcPath, dstPath } = this - let fileSize - const isUpload = type === 'upload' - const cb = this.onError + const { src, dst, dstPath } = this const th = this // internal state variables - let fsize let pdst = 0 let total = 0 - let hadError = false - let readbuf let bufsize = chunkSize * concurrency - function onerror (err) { - if (hadError) { - return + const { fsize } = this + + th.dstHandle = destHandle + + if (fsize <= 0) { + return th.onError() + } + + // Use less memory where possible + while (bufsize > fsize) { + if (concurrency === 1) { + bufsize = fsize + break } - hadError = true + bufsize -= chunkSize + --concurrency + } - let left = 0 - let cbfinal + const readbuf = th.tryCreateBuffer(bufsize) + if (readbuf instanceof Error) { + return th.onError(readbuf) + } - if (th.srcHandle || th.dstHandle) { - cbfinal = function () { - if (--left === 0) { - cb(err) - } - } - if (th.srcHandle && (isUpload || src.writable)) { - ++left - } - if (th.dstHandle && (!isUpload || dst.writable)) { - ++left - } - if (th.srcHandle && (isUpload || src.writable)) { - src.close(th.srcHandle, cbfinal) - } - if (th.dstHandle && (!isUpload || dst.writable)) { - dst.close(th.dstHandle, cbfinal) + if (mode !== undefined) { + dst.fchmod(th.dstHandle, mode, function tryAgain (err) { + if (err) { + // Try chmod() for sftp servers that may not support fchmod() for + // whatever reason + dst.chmod(dstPath, mode, function (err_) { + tryAgain() + }) + return } - } else { - cb(err) - } + startReads() + }) + } else { + startReads() } - src.open(srcPath, 'r', (err, sourceHandle) => { + function onread (err, nb, data, dstpos, datapos, origChunkLen) { if (err) { - return onerror(err) + return th.onError(err) } - th.srcHandle = sourceHandle - - if (fileSize === undefined) { - src.fstat(th.srcHandle, tryStat) - } else { - tryStat(null, { size: fileSize }) + if (th.onDestroy) { + return } - function tryStat (err, attrs) { - if (err) { - if (src !== fs) { - // Try stat() for sftp servers that may not support fstat() for - // whatever reason - src.stat(srcPath, (err_, attrs_) => { - if (err_) { - return onerror(err) - } - tryStat(null, attrs_) - }) - return - } - return onerror(err) - } - fsize = attrs.size - dst.open(dstPath, 'w', (err, destHandle) => { - if (err) { - return onerror(err) - } - - th.dstHandle = destHandle + datapos = datapos || 0 - if (fsize <= 0) { - return onerror() - } + dst.write(th.dstHandle, readbuf, datapos, nb, dstpos, writeCb) - // Use less memory where possible - while (bufsize > fsize) { - if (concurrency === 1) { - bufsize = fsize - break - } - bufsize -= chunkSize - --concurrency - } + function writeCb (err) { + if (err) { + return th.onError(err) + } - readbuf = tryCreateBuffer(bufsize) - if (readbuf instanceof Error) { - return onerror(readbuf) - } + total += nb + onstep && onstep(total, nb, fsize) - if (mode !== undefined) { - dst.fchmod(th.dstHandle, mode, function tryAgain (err) { - if (err) { - // Try chmod() for sftp servers that may not support fchmod() for - // whatever reason - dst.chmod(dstPath, mode, function (err_) { - tryAgain() - }) - return - } - startReads() - }) - } else { - startReads() - } + if (nb < origChunkLen) { + return singleRead(datapos, dstpos + nb, origChunkLen - nb) + } - function onread (err, nb, data, dstpos, datapos, origChunkLen) { + if (total === fsize) { + dst.close(th.dstHandle, (err) => { + th.dstHandle = undefined if (err) { - return onerror(err) + return th.onError(err) } - - datapos = datapos || 0 - - dst.write(th.dstHandle, readbuf, datapos, nb, dstpos, writeCb) - - function writeCb (err) { + src.close(th.srcHandle, (err) => { + th.srcHandle = undefined if (err) { - return onerror(err) - } - - total += nb - onstep && onstep(total, nb, fsize) - - if (nb < origChunkLen) { - return singleRead(datapos, dstpos + nb, origChunkLen - nb) - } - - if (total === fsize) { - dst.close(th.dstHandle, (err) => { - th.dstHandle = undefined - if (err) { - return onerror(err) - } - src.close(th.srcHandle, (err) => { - th.srcHandle = undefined - if (err) { - return onerror(err) - } - cb() - }) - }) - return + return th.onError(err) } + th.onError() + }) + }) + return + } - if (pdst >= fsize) { - return - } + if (pdst >= fsize) { + return + } - const chunk = (pdst + chunkSize > fsize ? fsize - pdst : chunkSize) - singleRead(datapos, pdst, chunk) - pdst += chunk - } - } + const chunk = (pdst + chunkSize > fsize ? fsize - pdst : chunkSize) + singleRead(datapos, pdst, chunk) + pdst += chunk + } + } - function makeCb (psrc, pdst, chunk) { - return function (err, nb, data) { - onread(err, nb, data, pdst, psrc, chunk) - } - } + function makeCb (psrc, pdst, chunk) { + return function (err, nb, data) { + onread(err, nb, data, pdst, psrc, chunk) + } + } - function singleRead (psrc, pdst, chunk) { - if (th.pausing) { - return setTimeout( - () => singleRead(psrc, pdst, chunk), 2 - ) - } - src.read( - th.srcHandle, - readbuf, - psrc, - chunk, - pdst, - makeCb(psrc, pdst, chunk) - ) - } + function singleRead (psrc, pdst, chunk) { + if (th.onDestroy) { + return + } + if (th.pausing) { + th.timers[psrc + ':' + pdst] = setTimeout(() => { + singleRead(psrc, pdst, chunk) + }, 2) + return + } + src.read( + th.srcHandle, + readbuf, + psrc, + chunk, + pdst, + makeCb(psrc, pdst, chunk) + ) + } - function startReads () { - let reads = 0 - let psrc = 0 - while (pdst < fsize && reads < concurrency) { - const chunk = (pdst + chunkSize > fsize ? fsize - pdst : chunkSize) - singleRead(psrc, pdst, chunk) - psrc += chunk - pdst += chunk - ++reads - } - } - }) + function startReads () { + let reads = 0 + let psrc = 0 + while (pdst < fsize && reads < concurrency) { + const chunk = (pdst + chunkSize > fsize ? fsize - pdst : chunkSize) + singleRead(psrc, pdst, chunk) + psrc += chunk + pdst += chunk + ++reads } - }) + } } onEnd = (id = this.id, ws = this.ws) => { @@ -270,7 +260,7 @@ export class Transfer { return this.onEnd() } ws && ws.s({ - wid: 'transfer:err:' + id, + id: 'transfer:err:' + id, error: { message: err.message, stack: err.stack @@ -286,14 +276,33 @@ export class Transfer { this.pausing = false } - destroy = () => { + kill = () => { if (this.src && this.srcHandle) { this.src.close(this.srcHandle, log.error) } if (this.dst && this.dstHandle) { this.dst.close(this.dstHandle, log.error) } - this.ws.close() + this.src = null + this.dst = null + this.srcHandle = null + this.dstHandle = null + } + + destroy = () => { + this.onDestroy = true + setTimeout(this.kill, 200) + if (this.ws) { + this.ws.close() + this.ws = null + } + if (this.timers) { + Object.keys(this.timers).forEach(k => { + clearTimeout(this.timers[k]) + this.timers[k] = null + }) + this.timers = null + } } // end diff --git a/src/client/entry-web/worker.js b/src/client/entry-web/worker.js index 89e5fd5..9164e69 100644 --- a/src/client/entry-web/worker.js +++ b/src/client/entry-web/worker.js @@ -136,6 +136,8 @@ async function onMsg (e) { } self.addEventListener('message', onMsg) -send({ - action: 'worker-init' -}) +setTimeout(() => { + send({ + action: 'worker-init' + }) +}, 10)