Compare commits

..

2 Commits
russh ... mica2

Author SHA1 Message Date
Eugene Pankov
7e3fe7e938 wip 2024-08-03 21:22:40 +02:00
Eugene Pankov
9b86ec0a4c tmp 2024-08-03 20:32:47 +02:00
70 changed files with 2406 additions and 2328 deletions

View File

@@ -1310,24 +1310,6 @@
"contributions": [
"code"
]
},
{
"login": "fireblue",
"name": "fireblue",
"avatar_url": "https://avatars.githubusercontent.com/u/1034929?v=4",
"profile": "https://github.com/fireblue",
"contributions": [
"code"
]
},
{
"login": "marko1616",
"name": "marko1616",
"avatar_url": "https://avatars.githubusercontent.com/u/45327989?v=4",
"profile": "https://github.com/marko1616",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@@ -37,15 +37,9 @@ jobs:
matrix:
include:
- arch: x86_64
rust_triple: x86_64-apple-darwin
- arch: arm64
rust_triple: aarch64-apple-darwin
fail-fast: false
env:
ARCH: ${{matrix.arch}}
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -57,8 +51,6 @@ jobs:
with:
node-version: 18
- run: rustup target add ${{matrix.rust_triple}}
- name: Install deps
run: |
sudo -H pip3 install setuptools
@@ -67,6 +59,12 @@ jobs:
env:
ARCH: ${{matrix.arch}}
- name: Fix cross build
run: |
rm -rf app/node_modules/cpu-features
rm -rf app/node_modules/ssh2/crypto/build
if: matrix.arch == 'arm64'
- name: Webpack
run: yarn run build
@@ -138,24 +136,18 @@ jobs:
include:
- build-arch: x64
arch: amd64
rust_triple: x86_64-unknown-linux-gnu
- build-arch: arm64
arch: arm64
rust_triple: aarch64-unknown-linux-gnu
triplet: aarch64-linux-gnu-
- build-arch: arm
arch: armhf
rust_triple: arm-unknown-linux-gnueabihf
triplet: arm-linux-gnueabihf-
fail-fast: false
env:
CC: ${{matrix.triplet}}gcc
CXX: ${{matrix.triplet}}g++
ARCH: ${{matrix.build-arch}}
npm_config_arch: ${{matrix.build-arch}}
npm_config_target_arch: ${{matrix.build-arch}}
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
steps:
- name: Checkout
@@ -168,8 +160,6 @@ jobs:
with:
node-version: 18
- run: rustup target add ${{matrix.rust_triple}}
- name: Install dependencies
run: |
sudo apt-get update
@@ -243,15 +233,13 @@ jobs:
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
- name: Upload packages to packagecloud.io
uses: TykTechnologies/packagecloud-action@main
uses: Eugeny/packagecloud-action@main
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
env:
PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}
with:
repo: 'eugeny/tabby'
dir: 'dist'
rpmvers: 'el/9 el/8 ol/6 ol/7'
debvers: 'ubuntu/bionic ubuntu/focal ubuntu/hirsute ubuntu/impish ubuntu/jammy ubuntu/kinetic ubuntu/noble debian/jessie debian/stretch debian/buster'
- uses: actions/upload-artifact@master
name: Upload AppImage (${{matrix.arch}})
@@ -290,22 +278,17 @@ jobs:
path: tabby-web.tar.gz
if: matrix.build-arch == 'x64'
Windows-Build:
runs-on: windows-latest
runs-on: windows-2022
needs: Lint
strategy:
matrix:
include:
- arch: x64
rust_triple: x86_64-pc-windows-msvc
- arch: arm64
rust_triple: aarch64-pc-windows-msvc
fail-fast: false
env:
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
ARCH: ${{matrix.arch}}
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -317,14 +300,6 @@ jobs:
with:
node-version: 18
- run: npm i -g npx
- run: rustup target add ${{matrix.rust_triple}}
- name: Update node-gyp
run: |
npm install --global node-gyp@10.2.0
npm prefix -g | % {npm config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js"}
- name: Build
shell: powershell
run: |

1
.gitignore vendored
View File

@@ -33,6 +33,7 @@ docs/api
sentry.properties
sentry-symbols.js
tabby-ssh/util/pagent.exe
*.psd
crowdin.yml

View File

@@ -340,8 +340,6 @@ Dank geht an diese wunderbaren Menschen ([emoji key](https://allcontributors.org
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -342,8 +342,6 @@ Gracias a estas maravillosas personas ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -339,8 +339,6 @@ Terima kasih kepada mereka yang telah membantu ([emoji key](https://allcontribut
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -335,8 +335,6 @@ Grazie a queste persone meravigliose ([emoji key](https://allcontributors.org/do
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -350,8 +350,6 @@ Windows上では、`Tabby.exe`がある場所と同じ場所に`data`フォル
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -334,8 +334,6 @@ Pull requests and plugins are welcome!
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -2,7 +2,7 @@
<p align="center">
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge"></a> &nbsp; <a href="https://nightly.link/Eugeny/tabby/workflows/build/master"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> &nbsp; <a href="https://discord.gg/Vn7BjmzhtF"><img alt="Discord" src="https://img.shields.io/discord/1280890060195233934?style=for-the-badge&color=blue&logo=discord&logoColor=white&label=Discord"></a> &nbsp <a href="https://translate.tabby.sh/"><img alt="Translate" src="https://shields.io/badge/Translate-UI-white?logo=googletranslate&style=for-the-badge&color=white&logoColor=fff"></a>
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge"></a> &nbsp; <a href="https://nightly.link/Eugeny/tabby/workflows/build/master"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> &nbsp; <a href="https://matrix.to/#/#tabby-general:matrix.org"><img alt="Matrix" src="https://img.shields.io/matrix/tabby-general:matrix.org?logo=matrix&style=for-the-badge&color=magenta"></a> &nbsp <a href="https://translate.tabby.sh/"><img alt="Translate" src="https://shields.io/badge/Translate-UI-white?logo=googletranslate&style=for-the-badge&color=white&logoColor=fff"></a> &nbsp; <a href="https://twitter.com/eugeeeeny"><img alt="Twitter" src="https://shields.io/badge/Subscribe-News-blue?logo=twitter&style=for-the-badge&color=blue"></a>
</p>
<p align="center">
@@ -16,6 +16,9 @@
> 👋 Managing remote environments? Check out [Warpgate, my smart SSH/HTTP/MySQL bastion server](https://github.com/warp-tech/warpgate), it works great with Tabby, you'll love it.
> 👋 [Tabby-web](https://github.com/Eugeny/tabby-web) is looking for sponsors. As I can't afford to host it myself any longer, I'm looking for a sponsor to cover the hosting costs. If you're interested, please [get in touch](https://twitter.com/eugeeeeny)!
----
### Downloads:
@@ -152,11 +155,6 @@ Plugins and themes can be installed directly from the Settings view inside Tabby
[**keygen**](https://keygen.sh/?via=eugene) has provided free release & auto-update hosting
<a href="https://iqhive.com/"><img src="https://private-user-images.githubusercontent.com/161476/361584584-ed292436-1d50-46bc-b479-78222c83ed22.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQ3MDg3NjgsIm5iZiI6MTcyNDcwODQ2OCwicGF0aCI6Ii8xNjE0NzYvMzYxNTg0NTg0LWVkMjkyNDM2LTFkNTAtNDZiYy1iNDc5LTc4MjIyYzgzZWQyMi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODI2JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyNlQyMTQxMDhaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1iYzNlZjIxN2JiYzBkYTU5NGE4YmZlZDJiNmIxZWE1ZTAyOTNhYjJlZTRhOGZjYTk4N2E4MzMzZjg0ZTNkZWQ0JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.pQzR2d71YV4TIxOH3Lg20HpNKrm_r2D-xfkEM_F2DTs" width="100"></a>
[**IQ Hive**](https://iqhive.com) is providing financial support for the project development
<a name="contributing"></a>
# Contributing
@@ -358,8 +356,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -343,8 +343,6 @@ Obrigado vai para essas pessoas maravilhosas ([emoji key](https://allcontributor
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -335,8 +335,6 @@ Pull-запросы и плагины приветствуются!
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -334,8 +334,6 @@
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -1,10 +1,8 @@
import * as glasstron from 'glasstron'
import { autoUpdater } from 'electron-updater'
import { Subject, Observable, debounceTime } from 'rxjs'
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions, TouchBar, nativeImage, WebContents } from 'electron'
import ElectronConfig = require('electron-config')
import { enable as enableRemote } from '@electron/remote/main'
import * as os from 'os'
import * as path from 'path'
import macOSRelease from 'macos-release'
import { compare as compareVersions } from 'compare-versions'
@@ -12,21 +10,11 @@ import { compare as compareVersions } from 'compare-versions'
import type { Application } from './app'
import { parseArgs } from './cli'
let DwmEnableBlurBehindWindow: any = null
if (process.platform === 'win32') {
DwmEnableBlurBehindWindow = require('@tabby-gang/windows-blurbehind').DwmEnableBlurBehindWindow
}
export interface WindowOptions {
hidden?: boolean
}
abstract class GlasstronWindow extends BrowserWindow {
blurType: string
abstract setBlur (_: boolean)
}
const macOSVibrancyType: any = process.platform === 'darwin' ? compareVersions(macOSRelease().version || '0.0', '10.14', '>=') ? 'fullscreen-ui' : 'dark' : null
const macOSVibrancyType: any = process.platform === 'darwin' ? compareVersions(macOSRelease().version || '0.0', '10.14', '>=') ? 'under-window' : 'dark' : null
const activityIcon = nativeImage.createFromPath(`${app.getAppPath()}/assets/activity.png`)
@@ -36,14 +24,11 @@ export class Window {
webContents: WebContents
private visible = new Subject<boolean>()
private closed = new Subject<void>()
private window?: GlasstronWindow
private window?: BrowserWindow
private windowConfig: ElectronConfig
private windowBounds?: Rectangle
private closing = false
private lastVibrancy: { enabled: boolean, type?: string } | null = null
private disableVibrancyWhileDragging = false
private touchBarControl: any
private isFluentVibrancy = false
private dockHidden = false
get visible$ (): Observable<boolean> { return this.visible }
@@ -71,6 +56,7 @@ export class Window {
},
maximizable: true,
frame: false,
transparent: true,
show: false,
backgroundColor: '#00000000',
acceptFirstMouse: true,
@@ -100,11 +86,15 @@ export class Window {
}
}
if (process.platform === 'darwin') {
this.window = new BrowserWindow(bwOptions) as GlasstronWindow
} else {
this.window = new glasstron.BrowserWindow(bwOptions)
}
this.window = new BrowserWindow(bwOptions)
// https://github.com/electron/electron/issues/39959#issuecomment-1758736966
this.window.on('blur', () => {
this.window.setBackgroundColor('#00000000')
})
this.window.on('focus', () => {
this.window.setBackgroundColor('#00000000')
})
this.webContents = this.window.webContents
@@ -177,26 +167,12 @@ export class Window {
this.window.webContents.send('host:became-main-window')
}
setVibrancy (enabled: boolean, type?: string, userRequested?: boolean): void {
if (userRequested ?? true) {
this.lastVibrancy = { enabled, type }
}
if (process.platform === 'win32') {
if (parseFloat(os.release()) >= 10) {
this.window.blurType = enabled ? type === 'fluent' ? 'acrylic' : 'blurbehind' : null
try {
this.window.setBlur(enabled)
this.isFluentVibrancy = enabled && type === 'fluent'
} catch (error) {
console.error('Failed to set window blur', error)
}
} else {
DwmEnableBlurBehindWindow(this.window.getNativeWindowHandle(), enabled)
}
} else if (process.platform === 'linux') {
this.window.setBackgroundColor(enabled ? '#00000000' : '#131d27')
this.window.setBlur(enabled)
} else {
setMaterial (material: 'mica'|'acrylic'|'auto'): void {
this.window.setBackgroundMaterial(material)
}
setVibrancy (enabled: boolean): void {
if (process.platform === 'darwin') {
this.window.setVibrancy(enabled ? macOSVibrancyType : null)
}
}
@@ -369,8 +345,12 @@ export class Window {
this.window?.setAlwaysOnTop(flag)
})
this.on('window-set-vibrancy', (_, enabled, type) => {
this.setVibrancy(enabled, type)
this.on('window-set-vibrancy', (_, enabled) => {
this.setVibrancy(enabled)
})
this.on('window-set-material', (_, material) => {
this.setMaterial(material)
})
this.on('window-set-window-controls-color', (_, theme) => {
@@ -413,26 +393,6 @@ export class Window {
return { action: 'deny' }
})
ipcMain.on('window-set-disable-vibrancy-while-dragging', (_event, value) => {
this.disableVibrancyWhileDragging = value && this.configStore.hacks?.disableVibrancyWhileDragging
})
let moveEndedTimeout: any = null
const onBoundsChange = () => {
if (!this.lastVibrancy?.enabled || !this.disableVibrancyWhileDragging || !this.isFluentVibrancy) {
return
}
this.setVibrancy(false, undefined, false)
if (moveEndedTimeout) {
clearTimeout(moveEndedTimeout)
}
moveEndedTimeout = setTimeout(() => {
this.setVibrancy(this.lastVibrancy.enabled, this.lastVibrancy.type)
}, 50)
}
this.window.on('move', onBoundsChange)
this.window.on('resize', onBoundsChange)
ipcMain.on('window-set-traffic-light-position', (_event, x, y) => {
this.window.setWindowButtonPosition({ x, y })
})

View File

@@ -16,27 +16,24 @@
},
"dependencies": {
"@electron/remote": "^2",
"node-pty": "^1.1.0-beta.14",
"node-pty": "^1.0",
"any-promise": "^1.3.0",
"electron-config": "2.0.0",
"electron-debug": "^3.2.0",
"electron-promise-ipc": "^2.2.4",
"electron-updater": "^5.2.1",
"fontmanager-redux": "1.1.0",
"glasstron": "0.1.1",
"js-yaml": "4.1.0",
"keytar": "^7.9.0",
"mz": "^2.7.0",
"native-process-working-directory": "^1.0.2",
"npm": "6",
"rxjs": "^7.5.7",
"russh": "0.0.3",
"source-map-support": "^0.5.20",
"v8-compile-cache": "^2.3.0",
"yargs": "^17.7.2"
},
"optionalDependencies": {
"@tabby-gang/windows-blurbehind": "^3.0.0",
"macos-native-processlist": "^2.1.0",
"patch-package": "^6.5.0",
"serialport": "11.0.1",

View File

@@ -44,7 +44,6 @@ const config = {
'electron-promise-ipc': 'commonjs electron-promise-ipc',
'electron-updater': 'commonjs electron-updater',
fs: 'commonjs fs',
glasstron: 'commonjs glasstron',
mz: 'commonjs mz',
npm: 'commonjs npm',
'node:os': 'commonjs os',
@@ -54,7 +53,6 @@ const config = {
'source-map-support': 'commonjs source-map-support',
'windows-swca': 'commonjs windows-swca',
'windows-native-registry': 'commonjs windows-native-registry',
'@tabby-gang/windows-blurbehind': 'commonjs @tabby-gang/windows-blurbehind',
'yargs/yargs': 'commonjs yargs/yargs',
},
plugins: [

View File

@@ -28,11 +28,6 @@
wrap-ansi "^8.1.0"
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
"@napi-rs/cli@^2.18.3":
version "2.18.4"
resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-2.18.4.tgz#12bebfb7995902fa7ab43cc0b155a7f5a2caa873"
integrity sha512-SgJeA4df9DE2iAEpr3M2H0OKl/yjtg1BnRI5/JyowS71tUWhrfSu2LT0V3vlHET+g1hBVlrO60PmEXwUEKp8Mg==
"@ngx-translate/core@^14.0.0":
version "14.0.0"
resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-14.0.0.tgz#af421d0e1a28376843f0fed375cd2fae7630a5ff"
@@ -178,13 +173,6 @@
dependencies:
debug "^4.3.2"
"@tabby-gang/windows-blurbehind@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@tabby-gang/windows-blurbehind/-/windows-blurbehind-3.0.0.tgz#48d409c2eb14a12c867b70de5ee4d6769ef45e8f"
integrity sha512-ah6eJcoQZWOZfu9sd2pWlOJmfl1v+2EZQMeIp7MWvg+/16WS16UFNdnOtlV6AUiABHfZo2QKfCNUEuorCM+Q2A==
dependencies:
"@types/node" "^10.12.18"
"@types/mz@2.7.4":
version "2.7.4"
resolved "https://registry.yarnpkg.com/@types/mz/-/mz-2.7.4.tgz#f9d1535cb5171199b28ae6abd6ec29e856551401"
@@ -197,11 +185,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe"
integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==
"@types/node@^10.12.18":
version "10.17.60"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
"@types/semver@^7.3.6":
version "7.3.9"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
@@ -1486,35 +1469,26 @@ github-from-package@0.0.0:
resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz"
integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
glasstron@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/glasstron/-/glasstron-0.1.1.tgz#491a2e6f7e7b285c3776c5f7af7aaba2269833b2"
integrity sha512-oLEMQM5wwdAQ44NrXD3wjk+b3dsfQG1XtkLn5pCxQNa3ri1AtWvvzpnhFUd88ZTmguHvkY4c3JKzcPSYaJAKKA==
dependencies:
node-addon-api "^4.0.0"
x11 "^2.3.0"
glob@^10.2.2, glob@^10.3.10:
version "10.4.5"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
version "10.3.10"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b"
integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==
dependencies:
foreground-child "^3.1.0"
jackspeak "^3.1.2"
minimatch "^9.0.4"
minipass "^7.1.2"
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
jackspeak "^2.3.5"
minimatch "^9.0.1"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-scurry "^1.10.1"
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
version "7.1.6"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.1.1"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
@@ -1937,10 +1911,10 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jackspeak@^3.1.2:
version "3.4.3"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
jackspeak@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
dependencies:
"@isaacs/cliui" "^8.0.2"
optionalDependencies:
@@ -2292,18 +2266,13 @@ lowercase-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
lru-cache@^10.0.1:
lru-cache@^10.0.1, "lru-cache@^9.1.1 || ^10.0.0":
version "10.0.2"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.2.tgz#34504678cc3266b09b8dfd6fab4e1515258271b7"
integrity sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==
dependencies:
semver "^7.3.5"
lru-cache@^10.2.0:
version "10.4.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
lru-cache@^4.0.1:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -2423,17 +2392,10 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimatch@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
minimatch@^9.0.4:
version "9.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
minimatch@^9.0.1:
version "9.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
dependencies:
brace-expansion "^2.0.1"
@@ -2506,11 +2468,6 @@ minipass@^5.0.0:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minizlib@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
@@ -2663,11 +2620,6 @@ node-addon-api@^4.0.0, node-addon-api@^4.3.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
node-addon-api@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-fetch-npm@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4"
@@ -2698,12 +2650,12 @@ node-gyp@^10.0.0, node-gyp@^5.0.2, node-gyp@^5.1.0:
tar "^6.1.2"
which "^4.0.0"
node-pty@^1.1.0-beta.14:
version "1.1.0-beta9"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta9.tgz#ed643cb3b398d031b4e31c216e8f3b0042435f1d"
integrity sha512-/Ue38pvXJdgRZ3+me1FgfglLd301GhJN0NStiotdt61tm43N5htUyR/IXOUzOKuNaFmCwIhy6nwb77Ky41LMbw==
node-pty@^1.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd"
integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==
dependencies:
node-addon-api "^7.1.0"
nan "^2.17.0"
nopt@^4.0.3:
version "4.0.3"
@@ -3125,11 +3077,6 @@ p-try@^2.0.0:
resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
package-json-from-dist@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00"
integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==
package-json@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
@@ -3242,12 +3189,12 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-scurry@^1.11.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
path-scurry@^1.10.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698"
integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==
dependencies:
lru-cache "^10.2.0"
lru-cache "^9.1.1 || ^10.0.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-type@^2.0.0:
@@ -3636,13 +3583,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
russh@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/russh/-/russh-0.0.3.tgz#bcb53d2efbe2b216857171bc5ca2131001ddfa46"
integrity sha512-iTW4M5W856zYjbjQYjlDFaSFSQ6pLBy+zsCYFwhivYuj8U5mZ7kF7TeGOUat9t4l25HVxAS36ivCt5l79p9lcQ==
dependencies:
"@napi-rs/cli" "^2.18.3"
rxjs@^7.5.2, rxjs@^7.5.7:
version "7.5.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39"

View File

@@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>

View File

@@ -51,7 +51,6 @@ mac:
entitlements: "./build/mac/entitlements.plist"
entitlementsInherit: "./build/mac/entitlements.plist"
extendInfo:
ElectronTeamID: ${teamId}
NSRequiresAquaSystemAppearance: false
NSCameraUsageDescription: "A subprocess requests access to the device's camera."
NSMicrophoneUsageDescription: "A subprocess requests access to the device's microphone."
@@ -61,7 +60,6 @@ mac:
NSDownloadsFolderUsageDescription: "A subprocess requests access to the user's Downloads folder."
NSNetworkVolumesUsageDescription: 'A subprocess requests access to files on a network volume.'
NSRemovableVolumesUsageDescription: 'A subprocess requests access to files on a removable volume.'
NSAppleEventsUsageDescription: 'A subprocess requests permission to send AppleScript events to another application.'
linux:
category: "Utility;TerminalEmulator;System"

View File

@@ -53,7 +53,7 @@
"html-loader": "4.2.0",
"json-loader": "^0.5.7",
"lru-cache": "^6.0.0",
"macos-release": "^3.3.0",
"macos-release": "^3.1.0",
"ngx-toastr": "^16.0.2",
"node-abi": "^3.65.0",
"npmlog": "6.0.2",
@@ -76,6 +76,7 @@
"source-code-pro": "^2.38.0",
"source-map-loader": "^4.0.1",
"source-sans-pro": "3.6.0",
"ssh2": "^1.14.0",
"style-loader": "^3.3.1",
"svg-inline-loader": "^0.8.2",
"thenby": "^1.3.4",

39
patches/ssh2+1.11.0.patch Normal file
View File

@@ -0,0 +1,39 @@
diff --git a/node_modules/ssh2/lib/protocol/keyParser.js b/node_modules/ssh2/lib/protocol/keyParser.js
index 9860e3f..ee82e51 100644
--- a/node_modules/ssh2/lib/protocol/keyParser.js
+++ b/node_modules/ssh2/lib/protocol/keyParser.js
@@ -15,6 +15,7 @@ const {
sign: sign_,
verify: verify_,
} = require('crypto');
+const { createVerify: createVerifyDSS } = require('browserify-sign')
const supportedOpenSSLCiphers = getCiphers();
const { Ber } = require('asn1');
@@ -404,6 +405,17 @@ const BaseKey = {
return new Error('No public key available');
if (!algo || typeof algo !== 'string')
algo = this[SYM_HASH_ALGO];
+
+ if (algo === 'dss1') {
+ const verifier = createVerifyDSS('DSA-SHA1');
+ verifier.update(data);
+ try {
+ return verifier.verify(pem, signature);
+ } catch (ex) {
+ return ex;
+ }
+ }
+
try {
return verify_(algo, data, pem, signature);
} catch (ex) {
@@ -1343,7 +1355,7 @@ function parseDER(data, baseType, comment, fullType) {
return new Error('Malformed OpenSSH public key');
pubPEM = genOpenSSLDSAPub(p, q, g, y);
pubSSH = genOpenSSHDSAPub(p, q, g, y);
- algo = 'sha1';
+ algo = 'dss1';
break;
}
case 'ssh-ed25519': {

View File

@@ -24,7 +24,6 @@ builder({
config: {
extraMetadata: {
version: vars.version,
teamId: process.env.APPLE_TEAM_ID,
},
mac: {
identity: !process.env.CI || process.env.CSC_LINK ? undefined : null,

View File

@@ -1,4 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -10,7 +10,7 @@ export { Theme } from './theme'
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
export { SelectorOption } from './selector'
export { CLIHandler, CLIEvent } from './cli'
export { PlatformService, ClipboardContent, MessageBoxResult, MessageBoxOptions, FileDownload, FileUpload, FileTransfer, HTMLFileUpload, FileUploadOptions, DirectoryUpload } from './platform'
export { PlatformService, ClipboardContent, MessageBoxResult, MessageBoxOptions, FileDownload, FileUpload, FileTransfer, HTMLFileUpload, FileUploadOptions } from './platform'
export { MenuItemOptions } from './menu'
export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
export { HostWindowService } from './hostWindow'

View File

@@ -63,24 +63,22 @@ export abstract class FileTransfer {
}
export abstract class FileDownload extends FileTransfer {
abstract write (buffer: Uint8Array): Promise<void>
abstract write (buffer: Buffer): Promise<void>
}
export abstract class FileUpload extends FileTransfer {
abstract read (): Promise<Uint8Array>
abstract read (): Promise<Buffer>
async readAll (): Promise<Uint8Array> {
const result = new Uint8Array(this.getSize())
let pos = 0
async readAll (): Promise<Buffer> {
const buffers: Buffer[] = []
while (true) {
const buf = await this.read()
if (!buf.length) {
break
}
result.set(buf, pos)
pos += buf.length
buffers.push(Buffer.from(buf))
}
return result
return Buffer.concat(buffers)
}
}
@@ -88,26 +86,6 @@ export interface FileUploadOptions {
multiple: boolean
}
export class DirectoryUpload {
private childrens: (FileUpload|DirectoryUpload)[] = []
constructor (private name = '') {
// Just set name for now.
}
getName (): string {
return this.name
}
getChildrens (): (FileUpload|DirectoryUpload)[] {
return this.childrens
}
pushChildren (item: FileUpload|DirectoryUpload): void {
this.childrens.push(item)
}
}
export type PlatformTheme = 'light'|'dark'
export abstract class PlatformService {
@@ -128,54 +106,23 @@ export abstract class PlatformService {
abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null>
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
abstract startUploadDirectory (paths?: string[]): Promise<DirectoryUpload>
async startUploadFromDragEvent (event: DragEvent, multiple = false): Promise<DirectoryUpload> {
const result = new DirectoryUpload()
startUploadFromDragEvent (event: DragEvent, multiple = false): FileUpload[] {
const result: FileUpload[] = []
if (!event.dataTransfer) {
return Promise.resolve(result)
return []
}
const traverseFileTree = (item: any, root: DirectoryUpload = result): Promise<void> => {
return new Promise((resolve) => {
if (item.isFile) {
item.file((file: File) => {
const transfer = new HTMLFileUpload(file)
this.fileTransferStarted.next(transfer)
root.pushChildren(transfer)
resolve()
})
} else if (item.isDirectory) {
const dirReader = item.createReader()
const childrenFolder = new DirectoryUpload(item.name)
dirReader.readEntries(async (entries: any[]) => {
for (const entry of entries) {
await traverseFileTree(entry, childrenFolder)
}
resolve()
})
root.pushChildren(childrenFolder)
} else {
resolve()
}
})
}
const promises: Promise<void>[] = []
const items = event.dataTransfer.items
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry()
if (item) {
promises.push(traverseFileTree(item))
if (!multiple) {
break
}
for (let i = 0; i < event.dataTransfer.files.length; i++) {
const file = event.dataTransfer.files[i]
const transfer = new HTMLFileUpload(file)
this.fileTransferStarted.next(transfer)
result.push(transfer)
if (!multiple) {
break
}
}
return Promise.all(promises).then(() => result)
return result
}
getConfigPath (): string|null {
@@ -263,12 +210,12 @@ export class HTMLFileUpload extends FileUpload {
return this.file.size
}
async read (): Promise<Uint8Array> {
async read (): Promise<Buffer> {
const result: any = await this.reader.read()
if (result.done || !result.value) {
return new Uint8Array(0)
return Buffer.from('')
}
const chunk = new Uint8Array(result.value)
const chunk = Buffer.from(result.value)
this.increaseProgress(chunk.length)
return chunk
}

View File

@@ -821,13 +821,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
if (this.disableDynamicTitle) {
return
}
const titles = [
this.getFocusedTab()?.title,
...this.getAllTabs()
.filter(x => x !== this.getFocusedTab())
.map(x => x.title),
]
this.setTitle([...new Set(titles)].join(' | '))
this.setTitle([...new Set(this.getAllTabs().map(x => x.title))].join(' | '))
}
private attachTabView (tab: BaseTabComponent) {
@@ -843,10 +837,6 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
})
}
tab.subscribeUntilDestroyed(
this.observeUntilChildDetached(tab, tab.focused$),
() => this.updateTitle(),
)
tab.subscribeUntilDestroyed(
this.observeUntilChildDetached(tab, tab.titleChange$),
() => this.updateTitle(),

View File

@@ -54,7 +54,5 @@ providerBlacklist: []
profileBlacklist: []
hacks:
disableGPU: false
disableVibrancyWhileDragging: false
enableFluentBackground: false
language: null
defaultQuickConnectProvider: "ssh"

View File

@@ -1,5 +1,5 @@
import { Directive, Output, ElementRef, EventEmitter, AfterViewInit } from '@angular/core'
import { DirectoryUpload, PlatformService } from '../api/platform'
import { FileUpload, PlatformService } from '../api/platform'
import './dropZone.directive.scss'
/** @hidden */
@@ -7,7 +7,7 @@ import './dropZone.directive.scss'
selector: '[dropZone]',
})
export class DropZoneDirective implements AfterViewInit {
@Output() transfer = new EventEmitter<DirectoryUpload>()
@Output() transfer = new EventEmitter<FileUpload>()
private dropHint?: HTMLElement
constructor (
@@ -27,9 +27,11 @@ export class DropZoneDirective implements AfterViewInit {
})
}
})
this.el.nativeElement.addEventListener('drop', async (event: DragEvent) => {
this.el.nativeElement.addEventListener('drop', (event: DragEvent) => {
this.removeHint()
this.transfer.emit(await this.platform.startUploadFromDragEvent(event, true))
for (const transfer of this.platform.startUploadFromDragEvent(event, true)) {
this.transfer.emit(transfer)
}
})
this.el.nativeElement.addEventListener('dragleave', () => {
this.removeHint()

View File

@@ -27,8 +27,12 @@ export class HomeBaseService {
this.platform.openExternal('https://github.com/Eugeny/tabby')
}
openDiscord (): void {
this.platform.openExternal('https://discord.gg/4c5EVTBhtp')
openDiscussions (): void {
this.platform.openExternal('https://github.com/Eugeny/tabby/discussions')
}
openTwitter (): void {
this.platform.openExternal('https://twitter.com/eugeeeeny')
}
openTranslations (): void {

View File

@@ -306,7 +306,7 @@ export class VaultFileProvider extends FileProvider {
id,
description: `${description} (${transfer.getName()})`,
},
value: Buffer.from(await transfer.readAll()).toString('base64'),
value: (await transfer.readAll()).toString('base64'),
})
return `${this.prefix}${id}`
}

View File

@@ -5,7 +5,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
export const WIN_BUILD_CONPTY_SUPPORTED = 17692
export const WIN_BUILD_CONPTY_STABLE = 18309
export const WIN_BUILD_WSL_EXE_DISTRO_FLAG = 17763
export const WIN_BUILD_FLUENT_BG_SUPPORTED = 17063
export const WIN_BUILD_WINDOW_MATERIAL_SUPPORTED = 22621
export function getWindows10Build (): number|undefined {
return process.platform === 'win32' && parseFloat(os.release()) >= 10 ? parseInt(os.release().split('.')[2]) : undefined

View File

@@ -1,5 +1,5 @@
import { NgModule } from '@angular/core'
import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider } from 'tabby-core'
import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider, WIN_BUILD_WINDOW_MATERIAL_SUPPORTED } from 'tabby-core'
import { TerminalColorSchemeProvider } from 'tabby-terminal'
import { SFTPContextMenuItemProvider, SSHProfileImporter, AutoPrivateKeyLocator } from 'tabby-ssh'
import { PTYInterface, ShellProvider, UACService } from 'tabby-local'
@@ -164,13 +164,23 @@ export default class ElectronModule {
}
private updateVibrancy () {
let vibrancyType = this.config.store.appearance.vibrancyType
if (this.hostApp.platform === Platform.Windows && !isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)) {
vibrancyType = null
}
this.electron.ipcRenderer.send('window-set-vibrancy', this.config.store.appearance.vibrancy, vibrancyType)
this.hostWindow.setOpacity(this.config.store.appearance.opacity)
if (isWindowsBuild(WIN_BUILD_WINDOW_MATERIAL_SUPPORTED)) {
this.electron.ipcRenderer.send(
'window-set-material',
this.config.store.appearance.vibrancy
? this.config.store.appearance.vibrancyType === 'fluent'
? 'mica'
: 'acrylic'
: 'none',
)
return
}
if (this.hostApp.platform === Platform.macOS) {
this.electron.ipcRenderer.send('window-set-vibrancy', this.config.store.appearance.vibrancy)
}
}
private updateWindowControlsColor () {

View File

@@ -1,5 +1,5 @@
import { Injectable, NgZone, Injector } from '@angular/core'
import { isWindowsBuild, WIN_BUILD_FLUENT_BG_SUPPORTED, HostAppService, Platform, CLIHandler } from 'tabby-core'
import { HostAppService, Platform, CLIHandler } from 'tabby-core'
import { ElectronService } from '../services/electron.service'
@@ -48,10 +48,6 @@ export class ElectronHostAppService extends HostAppService {
electron.ipcRenderer.on('host:config-change', () => this.zone.run(() => {
this.configChangeBroadcast.next()
}))
if (isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)) {
electron.ipcRenderer.send('window-set-disable-vibrancy-while-dragging', true)
}
}
newWindow (): void {

View File

@@ -5,7 +5,7 @@ import * as os from 'os'
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
import { execFile } from 'mz/child_process'
import { Injectable, NgZone } from '@angular/core'
import { PlatformService, ClipboardContent, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, DirectoryUpload, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core'
import { PlatformService, ClipboardContent, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core'
import { ElectronService } from '../services/electron.service'
import { ElectronHostWindow } from './hostWindow.service'
import { ShellIntegrationService } from './shellIntegration.service'
@@ -48,21 +48,6 @@ export class ElectronPlatformService extends PlatformService {
})
}
async getAllFiles (dir: string, root: DirectoryUpload): Promise<DirectoryUpload> {
const items = await fs.readdir(dir, { withFileTypes: true })
for (const item of items) {
if (item.isDirectory()) {
root.pushChildren(await this.getAllFiles(path.join(dir, item.name), new DirectoryUpload(item.name)))
} else {
const file = new ElectronFileUpload(path.join(dir, item.name), this.electron)
root.pushChildren(file)
await wrapPromise(this.zone, file.open())
this.fileTransferStarted.next(file)
}
}
return root
}
readClipboard (): string {
return this.electron.clipboard.readText()
}
@@ -231,28 +216,6 @@ export class ElectronPlatformService extends PlatformService {
}))
}
async startUploadDirectory (paths?: string[]): Promise<DirectoryUpload> {
const properties: any[] = ['openFile', 'treatPackageAsDirectory', 'openDirectory']
if (!paths) {
const result = await this.electron.dialog.showOpenDialog(
this.hostWindow.getWindow(),
{
buttonLabel: this.translate.instant('Select'),
properties,
},
)
if (result.canceled) {
return new DirectoryUpload()
}
paths = result.filePaths
}
const root = new DirectoryUpload()
root.pushChildren(await this.getAllFiles(paths[0].split(path.sep).join(path.posix.sep), new DirectoryUpload(path.basename(paths[0]))))
return root
}
async startDownload (name: string, mode: number, size: number, filePath?: string): Promise<FileDownload|null> {
if (!filePath) {
const result = await this.electron.dialog.showSaveDialog(
@@ -300,12 +263,12 @@ class ElectronFileUpload extends FileUpload {
private size: number
private mode: number
private file: fs.FileHandle
private buffer: Uint8Array
private buffer: Buffer
private powerSaveBlocker = 0
constructor (private filePath: string, private electron: ElectronService) {
super()
this.buffer = new Uint8Array(256 * 1024)
this.buffer = Buffer.alloc(256 * 1024)
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
}
@@ -328,7 +291,7 @@ class ElectronFileUpload extends FileUpload {
return this.size
}
async read (): Promise<Uint8Array> {
async read (): Promise<Buffer> {
const result = await this.file.read(this.buffer, 0, this.buffer.length, null)
this.increaseProgress(result.bytesRead)
return this.buffer.slice(0, result.bytesRead)
@@ -370,7 +333,7 @@ class ElectronFileDownload extends FileDownload {
return this.size
}
async write (buffer: Uint8Array): Promise<void> {
async write (buffer: Buffer): Promise<void> {
let pos = 0
while (pos < buffer.length) {
const result = await this.file.write(buffer, pos, buffer.length - pos, null)

View File

@@ -49,24 +49,19 @@ export class EditSFTPContextMenu extends SFTPContextMenuItemProvider {
this.platform.openPath(tempPath)
const events = new Subject<string>()
fs.chmodSync(tempPath, 0o700)
// skip the first burst of events
setTimeout(() => {
const watcher = fs.watch(tempPath, event => events.next(event))
events.pipe(debounceTime(1000), debounce(async event => {
if (event === 'rename') {
watcher.close()
}
const upload = await this.platform.startUpload({ multiple: false }, [tempPath])
if (!upload.length) {
return
}
await sftp.upload(item.fullPath, upload[0])
await sftp.chmod(item.fullPath, item.mode)
})).subscribe()
watcher.on('close', () => events.complete())
sftp.closed$.subscribe(() => watcher.close())
}, 1000)
const watcher = fs.watch(tempPath, event => events.next(event))
events.pipe(debounceTime(1000), debounce(async event => {
if (event === 'rename') {
watcher.close()
}
const upload = await this.platform.startUpload({ multiple: false }, [tempPath])
if (!upload.length) {
return
}
await sftp.upload(item.fullPath, upload[0])
await sftp.chmod(item.fullPath, item.mode)
})).subscribe()
watcher.on('close', () => events.complete())
sftp.closed$.subscribe(() => watcher.close())
}
}

View File

@@ -1,18 +1,18 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
dataurl@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dataurl/-/dataurl-0.1.0.tgz#1f4734feddec05ffe445747978d86759c4b33199"
integrity sha1-H0c0/t3sBf/kRXR5eNhnWcSzMZk=
runes@^0.4.2:
version "0.4.3"
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
dataurl@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dataurl/-/dataurl-0.1.0.tgz#1f4734feddec05ffe445747978d86759c4b33199"
integrity sha1-H0c0/t3sBf/kRXR5eNhnWcSzMZk=
runes@^0.4.2:
version "0.4.3"
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==

View File

@@ -1,13 +1,13 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@14.14.14":
version "14.14.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@14.14.14":
version "14.14.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==

View File

@@ -40,11 +40,11 @@
div(translate) Report a problem
small.text-muted(translate) Generate a pre-filled GitHub issue
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscord()')
i.fab.fa-fw.fa-discord
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscussions()')
i.fas.fa-fw.fa-comments
div
div(translate) Community
small.text-muted(translate) On Discord
div(translate) Ask a question
small.text-muted(translate) On GitHub Discussions
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openGitHub()')
i.fab.fa-fw.fa-github
@@ -58,6 +58,12 @@
div(translate) What's new
small.text-muted(translate) Show release notes
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openTwitter()')
i.fab.fa-fw.fa-twitter
div
div(translate) Subscribe to updates
small.text-muted(translate) Tabby news and updates on Twitter
h3(translate) Application settings
.form-line

View File

@@ -123,7 +123,7 @@ export class VaultSettingsTabComponent extends BaseComponent {
}
await this.vault.updateSecret(secret, {
...secret,
value: Buffer.from(await transfers[0].readAll()).toString('base64'),
value: (await transfers[0].readAll()).toString('base64'),
})
this.loadVault()
}

View File

@@ -32,18 +32,18 @@ h3.mb-3(translate) Window
)
.form-line(*ngIf='platform.supportsWindowControls')
.form-line(*ngIf='platform.supportsWindowControls && (hostApp.platform !== Platform.Windows || isWindowMaterialSupported)')
.header
.title(*ngIf='hostApp.platform !== Platform.macOS', translate) Acrylic background
.title(*ngIf='hostApp.platform !== Platform.macOS', translate) Blurred background
.title(*ngIf='hostApp.platform === Platform.macOS', translate) Vibrancy
.description(translate) Gives the window a blurred transparent background
.description(*ngIf='hostApp.platform === Platform.Linux', translate) Gives the window a blurred transparent background
toggle(
[(ngModel)]='config.store.appearance.vibrancy',
(ngModelChange)='saveConfiguration()'
)
.form-line(*ngIf='config.store.appearance.vibrancy && isFluentVibrancySupported && config.store.hacks.enableFluentBackground')
.form-line(*ngIf='config.store.appearance.vibrancy && isWindowMaterialSupported')
.header
.title(translate) Background type
.btn-group
@@ -58,7 +58,7 @@ h3.mb-3(translate) Window
label.btn.btn-secondary(
for='vibrancyTypeBlur'
)
span(translate) Blur
span(translate) Acrylic
input.btn-check(
type='radio',
name='vibracy',
@@ -70,7 +70,7 @@ h3.mb-3(translate) Window
label.btn.btn-secondary(
for='vibrancyTypeFluent'
)
span Fluent
span Mica
.form-line(*ngIf='platform.supportsWindowControls')
.header
@@ -422,23 +422,3 @@ h3.mt-4(translate) Hacks
[(ngModel)]='config.store.hacks.disableGPU',
(ngModelChange)='config.save(); config.requestRestart()'
)
.form-line(*ngIf='hostApp.platform === Platform.Windows && isFluentVibrancySupported')
.header
.title(translate) Enable fluent background option
.description(translate) Experimental Windows 10 background style known to cause issues
toggle(
[(ngModel)]='config.store.hacks.enableFluentBackground',
(ngModelChange)='config.save()'
)
.form-line(*ngIf='hostApp.platform === Platform.Windows && isFluentVibrancySupported')
.header
.title(translate) Disable fluent background while dragging
.description(translate) Fluent background sometimes causes drag lag
toggle(
[(ngModel)]='config.store.hacks.disableVibrancyWhileDragging',
(ngModelChange)='config.save(); config.requestRestart()'
)

View File

@@ -8,10 +8,10 @@ import {
HostAppService,
Platform,
isWindowsBuild,
WIN_BUILD_FLUENT_BG_SUPPORTED,
BaseComponent,
Screen,
PlatformService,
WIN_BUILD_WINDOW_MATERIAL_SUPPORTED,
} from 'tabby-core'
@@ -23,7 +23,7 @@ import {
export class WindowSettingsTabComponent extends BaseComponent {
screens: Screen[]
Platform = Platform
isFluentVibrancySupported = false
isWindowMaterialSupported = false
@HostBinding('class.content-box') true
@@ -47,7 +47,7 @@ export class WindowSettingsTabComponent extends BaseComponent {
this.screens = dockingService.getScreens()
}
this.isFluentVibrancySupported = isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)
this.isWindowMaterialSupported = isWindowsBuild(WIN_BUILD_WINDOW_MATERIAL_SUPPORTED)
}
@debounce(500)

View File

@@ -11,17 +11,22 @@
"build": "webpack --progress --color",
"watch": "webpack --progress --color --watch",
"postinstall": "run-script-os",
"postinstall:darwin:linux": "exit"
"postinstall:darwin:linux": "exit",
"postinstall:win32": "xcopy /i /y ..\\node_modules\\ssh2\\util\\pagent.exe util\\"
},
"files": [
"dist",
"util/pagent.exe",
"typings"
],
"author": "Eugene Pankov",
"license": "MIT",
"devDependencies": {
"@types/node": "20.3.1",
"@types/ssh2": "^0.5.46",
"ansi-colors": "^4.1.1",
"diffie-hellman": "^5.0.3",
"sshpk": "Eugeny/node-sshpk#c2b71d1243714d2daf0988f84c3323d180817136",
"strip-ansi": "^7.0.0"
},
"dependencies": {
@@ -40,8 +45,5 @@
"tabby-core": "*",
"tabby-settings": "*",
"tabby-terminal": "*"
},
"resolutions": {
"glob": "7.2.3"
}
}

View File

@@ -1,45 +1,20 @@
import * as russh from 'russh'
import { SSHAlgorithmType } from './api'
import * as ALGORITHMS from 'ssh2/lib/protocol/constants'
import { ALGORITHM_BLACKLIST, SSHAlgorithmType } from './api'
export const supportedAlgorithms = {
[SSHAlgorithmType.KEX]: russh.getSupportedKexAlgorithms().filter(x => x !== 'none'),
[SSHAlgorithmType.HOSTKEY]: russh.getSupportedKeyTypes().filter(x => x !== 'none'),
[SSHAlgorithmType.CIPHER]: russh.getSupportedCiphers().filter(x => x !== 'clear'),
[SSHAlgorithmType.HMAC]: russh.getSupportedMACs().filter(x => x !== 'none'),
}
// Counteracts https://github.com/mscdex/ssh2/commit/f1b5ac3c81734c194740016eab79a699efae83d8
ALGORITHMS.DEFAULT_CIPHER.push('aes128-gcm')
ALGORITHMS.DEFAULT_CIPHER.push('aes256-gcm')
ALGORITHMS.SUPPORTED_CIPHER.push('aes128-gcm')
ALGORITHMS.SUPPORTED_CIPHER.push('aes256-gcm')
export const defaultAlgorithms = {
[SSHAlgorithmType.KEX]: [
'curve25519-sha256',
'curve25519-sha256@libssh.org',
'diffie-hellman-group16-sha512',
'diffie-hellman-group14-sha256',
'ext-info-c',
'ext-info-s',
'kex-strict-c-v00@openssh.com',
'kex-strict-s-v00@openssh.com',
],
[SSHAlgorithmType.HOSTKEY]: [
'ssh-ed25519',
'ecdsa-sha2-nistp256',
'ecdsa-sha2-nistp521',
'rsa-sha2-256',
'rsa-sha2-512',
'ssh-rsa',
],
[SSHAlgorithmType.CIPHER]: [
'chacha20-poly1305@openssh.com',
'aes256-gcm@openssh.com',
'aes256-ctr',
'aes192-ctr',
'aes128-ctr',
],
[SSHAlgorithmType.HMAC]: [
'hmac-sha2-512-etm@openssh.com',
'hmac-sha2-256-etm@openssh.com',
'hmac-sha2-512',
'hmac-sha2-256',
'hmac-sha1-etm@openssh.com',
'hmac-sha1',
],
export const supportedAlgorithms: Record<string, string> = {}
for (const k of Object.values(SSHAlgorithmType)) {
const supportedAlg = {
[SSHAlgorithmType.KEX]: 'SUPPORTED_KEX',
[SSHAlgorithmType.HOSTKEY]: 'SUPPORTED_SERVER_HOST_KEY',
[SSHAlgorithmType.CIPHER]: 'SUPPORTED_CIPHER',
[SSHAlgorithmType.HMAC]: 'SUPPORTED_MAC',
}[k]
supportedAlgorithms[k] = ALGORITHMS[supportedAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x)).sort()
}

View File

@@ -1,4 +1,5 @@
export * from './contextMenu'
export * from './interfaces'
export * from './importer'
export * from './proxyStream'
export { SSHMultiplexerService } from '../services/sshMultiplexer.service'

View File

@@ -51,3 +51,13 @@ export interface ForwardedPortConfig {
targetPort: number
description: string
}
export let ALGORITHM_BLACKLIST = [
// cause native crashes in node crypto, use EC instead
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group-exchange-sha1',
]
if (!process.env.TABBY_ENABLE_SSH_ALG_BLACKLIST) {
ALGORITHM_BLACKLIST = []
}

View File

@@ -0,0 +1,61 @@
import { Observable, Subject } from 'rxjs'
import { Duplex } from 'stream'
export class SSHProxyStreamSocket extends Duplex {
constructor (private parent: SSHProxyStream) {
super({
allowHalfOpen: false,
})
}
_read (size: number): void {
this.parent.requestData(size)
}
_write (chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void {
this.parent.consumeInput(chunk).then(() => callback(null), e => callback(e))
}
_destroy (error: Error|null, callback: (error: Error|null) => void): void {
this.parent.handleStopRequest(error).then(() => callback(null), e => callback(e))
}
}
export abstract class SSHProxyStream {
get message$ (): Observable<string> { return this.message }
get destroyed$ (): Observable<Error|null> { return this.destroyed }
get socket (): SSHProxyStreamSocket|null { return this._socket }
private message = new Subject<string>()
private destroyed = new Subject<Error|null>()
private _socket: SSHProxyStreamSocket|null = null
async start (): Promise<SSHProxyStreamSocket> {
if (!this._socket) {
this._socket = new SSHProxyStreamSocket(this)
}
return this._socket
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
abstract requestData (size: number): void
abstract consumeInput (data: Buffer): Promise<void>
protected emitMessage (message: string): void {
this.message.next(message)
}
protected emitOutput (data: Buffer): void {
this._socket?.push(data)
}
async handleStopRequest (error: Error|null): Promise<void> {
this.destroyed.next(error)
this.destroyed.complete()
this.message.complete()
}
stop (error?: Error): void {
this._socket?.destroy(error)
}
}

View File

@@ -14,19 +14,11 @@ input.form-control.mt-2(
)
.d-flex.mt-3
checkbox(
*ngIf='isPassword()',
[(ngModel)]='remember',
[text]='"Save password"|translate'
)
.ms-auto
button.btn.btn-secondary.me-3(
button.btn.btn-secondary(
*ngIf='step > 0',
(click)='previous()'
)
.ms-auto
button.btn.btn-primary(
(click)='next()'
)

View File

@@ -1,7 +1,6 @@
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectionStrategy } from '@angular/core'
import { KeyboardInteractivePrompt } from '../session/ssh'
import { SSHProfile } from '../api'
import { PasswordStorageService } from '../services/passwordStorage.service'
@Component({
selector: 'keyboard-interactive-auth-panel',
@@ -10,17 +9,13 @@ import { PasswordStorageService } from '../services/passwordStorage.service'
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class KeyboardInteractiveAuthComponent {
@Input() profile: SSHProfile
@Input() prompt: KeyboardInteractivePrompt
@Input() step = 0
@Output() done = new EventEmitter()
@ViewChild('input') input: ElementRef
remember = false
constructor (private passwordStorage: PasswordStorageService) {}
isPassword (): boolean {
return this.prompt.isAPasswordPrompt(this.step)
return this.prompt.prompts[this.step].prompt.toLowerCase().includes('password') || !this.prompt.prompts[this.step].echo
}
previous (): void {
@@ -31,10 +26,6 @@ export class KeyboardInteractiveAuthComponent {
}
next (): void {
if (this.isPassword() && this.remember) {
this.passwordStorage.savePassword(this.profile, this.prompt.responses[this.step])
}
if (this.step === this.prompt.prompts.length - 1) {
this.prompt.respond()
this.done.emit()

View File

@@ -23,15 +23,11 @@
button.btn.btn-link.btn-sm.flex-shrink-0.d-flex((click)='upload()')
i.fas.fa-upload.me-1
div(translate) Upload files
button.btn.btn-link.btn-sm.flex-shrink-0.d-flex((click)='uploadFolder()')
i.fas.fa-upload.me-1
div(translate) Upload folder
div(translate) Upload
button.btn.btn-link.text-decoration-none((click)='close()') !{require('../../../tabby-core/src/icons/times.svg')}
.body(dropZone, (transfer)='uploadOneFolder($event)')
.body(dropZone, (transfer)='uploadOne($event)')
a.alert.alert-info.d-flex.align-items-center(
*ngIf='shouldShowCWDTip && !cwdDetectionAvailable',
(click)='platform.openExternal("https://tabby.sh/go/cwd-detection")'

View File

@@ -1,7 +1,7 @@
import * as C from 'constants'
import { posix as path } from 'path'
import { Component, Input, Output, EventEmitter, Inject, Optional } from '@angular/core'
import { FileUpload, DirectoryUpload, MenuItemOptions, NotificationsService, PlatformService } from 'tabby-core'
import { FileUpload, MenuItemOptions, NotificationsService, PlatformService } from 'tabby-core'
import { SFTPSession, SFTPFile } from '../session/sftp'
import { SSHSession } from '../session/ssh'
import { SFTPContextMenuItemProvider } from '../api'
@@ -180,30 +180,6 @@ export class SFTPPanelComponent {
await Promise.all(transfers.map(t => this.uploadOne(t)))
}
async uploadFolder (): Promise<void> {
const transfer = await this.platform.startUploadDirectory()
await this.uploadOneFolder(transfer)
}
async uploadOneFolder (transfer: DirectoryUpload, accumPath = ''): Promise<void> {
const savedPath = this.path
for(const t of transfer.getChildrens()) {
if (t instanceof DirectoryUpload) {
try {
await this.sftp.mkdir(path.posix.join(this.path, accumPath, t.getName()))
} catch {
// Intentionally ignoring errors from making duplicate dirs.
}
await this.uploadOneFolder(t, path.posix.join(accumPath, t.getName()))
} else {
await this.sftp.upload(path.posix.join(this.path, accumPath, t.getName()), t)
}
}
if (this.path === savedPath) {
await this.navigate(this.path)
}
}
async uploadOne (transfer: FileUpload): Promise<void> {
const savedPath = this.path
await this.sftp.upload(path.join(this.path, transfer.getName()), transfer)

View File

@@ -51,7 +51,6 @@ sftp-panel.bg-dark(
keyboard-interactive-auth-panel.bg-dark(
*ngIf='activeKIPrompt',
[prompt]='activeKIPrompt',
[profile]='profile',
(click)='$event.stopPropagation()',
(done)='activeKIPrompt = null; frontend?.focus()'
)

View File

@@ -1,4 +1,3 @@
import * as russh from 'russh'
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
import colors from 'ansi-colors'
import { Component, Injector, HostListener } from '@angular/core'
@@ -95,21 +94,17 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile>
}
})
if (!(jumpSession.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Jump session is not authenticated yet somehow')
}
try {
session.jumpChannel = await jumpSession.ssh.openTCPForwardChannel({
addressToConnectTo: profile.options.host,
portToConnectTo: profile.options.port ?? 22,
originatorAddress: '127.0.0.1',
originatorPort: 0,
})
} catch (err) {
jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
throw err
}
session.jumpStream = await new Promise((resolve, reject) => jumpSession.ssh.forwardOut(
'127.0.0.1', 0, profile.options.host, profile.options.port ?? 22,
(err, stream) => {
if (err) {
jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
reject(err)
return
}
resolve(stream)
},
))
}
}
@@ -130,7 +125,7 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile>
})
if (!session.open) {
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` Connecting to ${session.profile.name}\r\n`)
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` Connecting to ${session.profile.options.host}\r\n`)
this.startSpinner(this.translate.instant(_('Connecting')))

View File

@@ -1,3 +1,5 @@
import './polyfills'
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'

View File

@@ -0,0 +1,12 @@
import 'ssh2'
const nodeCrypto = require('crypto')
const browserDH = require('diffie-hellman/browser')
nodeCrypto.createDiffieHellmanGroup = browserDH.createDiffieHellmanGroup
nodeCrypto.createDiffieHellman = browserDH.createDiffieHellman
// Declare function missing from @types
declare module 'ssh2' {
interface Client {
setNoDelay: (enable?: boolean) => this
}
}

View File

@@ -1,11 +1,11 @@
import { Injectable, InjectFlags, Injector } from '@angular/core'
import { NewTabParameters, PartialProfile, TranslateService, QuickConnectProfileProvider } from 'tabby-core'
import * as ALGORITHMS from 'ssh2/lib/protocol/constants'
import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component'
import { SSHTabComponent } from './components/sshTab.component'
import { PasswordStorageService } from './services/passwordStorage.service'
import { SSHAlgorithmType, SSHProfile } from './api'
import { ALGORITHM_BLACKLIST, SSHAlgorithmType, SSHProfile } from './api'
import { SSHProfileImporter } from './api/importer'
import { defaultAlgorithms } from './algorithms'
@Injectable({ providedIn: 'root' })
export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile> {
@@ -29,10 +29,10 @@ export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile>
agentForward: false,
warnOnClose: null,
algorithms: {
hmac: [] as string[],
kex: [] as string[],
cipher: [] as string[],
serverHostKey: [] as string[],
hmac: [],
kex: [],
cipher: [],
serverHostKey: [],
},
proxyCommand: null,
forwardedPorts: [],
@@ -54,7 +54,13 @@ export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile>
) {
super()
for (const k of Object.values(SSHAlgorithmType)) {
this.configDefaults.options.algorithms[k] = [...defaultAlgorithms[k]]
const defaultAlg = {
[SSHAlgorithmType.KEX]: 'DEFAULT_KEX',
[SSHAlgorithmType.HOSTKEY]: 'DEFAULT_SERVER_HOST_KEY',
[SSHAlgorithmType.CIPHER]: 'DEFAULT_CIPHER',
[SSHAlgorithmType.HMAC]: 'DEFAULT_MAC',
}[k]
this.configDefaults.options.algorithms[k] = ALGORITHMS[defaultAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x))
this.configDefaults.options.algorithms[k].sort()
}
}

View File

@@ -1,9 +1,15 @@
// import * as fs from 'fs/promises'
import * as shellQuote from 'shell-quote'
import * as net from 'net'
import * as fs from 'fs/promises'
import * as tmp from 'tmp-promise'
import socksv5 from '@luminati-io/socksv5'
import { Duplex } from 'stream'
import { Injectable } from '@angular/core'
import { spawn } from 'child_process'
import { ChildProcess } from 'node:child_process'
import { ConfigService, HostAppService, Platform, PlatformService } from 'tabby-core'
import { SSHSession } from '../session/ssh'
import { SSHProfile } from '../api'
import { SSHProfile, SSHProxyStream, SSHProxyStreamSocket } from '../api'
import { PasswordStorageService } from './passwordStorage.service'
@Injectable({ providedIn: 'root' })
@@ -49,7 +55,7 @@ export class SSHService {
let tmpFile: tmp.FileResult|null = null
if (session.activePrivateKey) {
tmpFile = await tmp.file()
// await fs.writeFile(tmpFile.path, session.activePrivateKey)
await fs.writeFile(tmpFile.path, session.activePrivateKey)
const winSCPcom = path.slice(0, -3) + 'com'
await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, `/output=${tmpFile.path}`])
args.push(`/privatekey=${tmpFile.path}`)
@@ -58,3 +64,171 @@ export class SSHService {
tmpFile?.cleanup()
}
}
export class ProxyCommandStream extends SSHProxyStream {
private process: ChildProcess|null
constructor (private command: string) {
super()
}
async start (): Promise<SSHProxyStreamSocket> {
const argv = shellQuote.parse(this.command)
this.process = spawn(argv[0], argv.slice(1), {
windowsHide: true,
stdio: ['pipe', 'pipe', 'pipe'],
})
this.process.on('error', error => {
this.stop(new Error(`Proxy command has failed to start: ${error.message}`))
})
this.process.on('exit', code => {
this.stop(new Error(`Proxy command has exited with code ${code}`))
})
this.process.stdout?.on('data', data => {
this.emitOutput(data)
})
this.process.stdout?.on('error', (err) => {
this.stop(err)
})
this.process.stderr?.on('data', data => {
this.emitMessage(data.toString())
})
return super.start()
}
requestData (size: number): void {
this.process?.stdout?.read(size)
}
async consumeInput (data: Buffer): Promise<void> {
const process = this.process
if (process) {
await new Promise(resolve => process.stdin?.write(data, resolve))
}
}
async stop (error?: Error): Promise<void> {
this.process?.kill()
super.stop(error)
}
}
export class SocksProxyStream extends SSHProxyStream {
private client: Duplex|null
private header: Buffer|null
constructor (private profile: SSHProfile) {
super()
}
async start (): Promise<SSHProxyStreamSocket> {
this.client = await new Promise((resolve, reject) => {
const connector = socksv5.connect({
host: this.profile.options.host,
port: this.profile.options.port,
proxyHost: this.profile.options.socksProxyHost ?? '127.0.0.1',
proxyPort: this.profile.options.socksProxyPort ?? 5000,
auths: [socksv5.auth.None()],
strictLocalDNS: false,
}, s => {
resolve(s)
this.header = s.read()
if (this.header) {
this.emitOutput(this.header)
}
})
connector.on('error', (err) => {
reject(err)
this.stop(new Error(`SOCKS connection failed: ${err.message}`))
})
})
this.client?.on('data', data => {
if (!this.header || data !== this.header) {
// socksv5 doesn't reliably emit the first data event
this.emitOutput(data)
this.header = null
}
})
this.client?.on('close', error => {
this.stop(error)
})
return super.start()
}
requestData (size: number): void {
this.client?.read(size)
}
async consumeInput (data: Buffer): Promise<void> {
return new Promise((resolve, reject) => {
this.client?.write(data, undefined, err => err ? reject(err) : resolve())
})
}
async stop (error?: Error): Promise<void> {
this.client?.destroy()
super.stop(error)
}
}
export class HTTPProxyStream extends SSHProxyStream {
private client: Duplex|null
private connected = false
constructor (private profile: SSHProfile) {
super()
}
async start (): Promise<SSHProxyStreamSocket> {
this.client = await new Promise((resolve, reject) => {
const connector = net.createConnection({
host: this.profile.options.httpProxyHost!,
port: this.profile.options.httpProxyPort!,
}, () => resolve(connector))
connector.on('error', error => {
reject(error)
this.stop(new Error(`Proxy connection failed: ${error.message}`))
})
})
this.client?.write(Buffer.from(`CONNECT ${this.profile.options.host}:${this.profile.options.port} HTTP/1.1\r\n\r\n`))
this.client?.on('data', (data: Buffer) => {
if (this.connected) {
this.emitOutput(data)
} else {
if (data.slice(0, 5).equals(Buffer.from('HTTP/'))) {
const idx = data.indexOf('\n\n')
const headers = data.slice(0, idx).toString()
const code = parseInt(headers.split(' ')[1])
if (code >= 200 && code < 300) {
this.emitMessage('Connected')
this.emitOutput(data.slice(idx + 2))
this.connected = true
} else {
this.stop(new Error(`Connection failed, code ${code}`))
}
}
}
})
this.client?.on('close', error => {
this.stop(error)
})
return super.start()
}
requestData (size: number): void {
this.client?.read(size)
}
async consumeInput (data: Buffer): Promise<void> {
return new Promise((resolve, reject) => {
this.client?.write(data, undefined, err => err ? reject(err) : resolve())
})
}
async stop (error?: Error): Promise<void> {
this.client?.destroy()
super.stop(error)
}
}

View File

@@ -1,9 +1,12 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as C from 'constants'
import { Subject, Observable } from 'rxjs'
import { posix as posixPath } from 'path'
import { Injector } from '@angular/core'
import { FileDownload, FileUpload, Logger, LogService } from 'tabby-core'
import * as russh from 'russh'
import { Injector, NgZone } from '@angular/core'
import { FileDownload, FileUpload, Logger, LogService, wrapPromise } from 'tabby-core'
import { SFTPWrapper } from 'ssh2'
import { promisify } from 'util'
import type { FileEntry, Stats } from 'ssh2-streams'
export interface SFTPFile {
name: string
@@ -19,37 +22,63 @@ export class SFTPFileHandle {
position = 0
constructor (
private inner: russh.SFTPFile|null,
private sftp: SFTPWrapper,
private handle: Buffer,
private zone: NgZone,
) { }
async read (): Promise<Uint8Array> {
if (!this.inner) {
return Promise.resolve(new Uint8Array(0))
}
return this.inner.read(256 * 1024)
read (): Promise<Buffer> {
const buffer = Buffer.alloc(256 * 1024)
return wrapPromise(this.zone, new Promise((resolve, reject) => {
while (true) {
const wait = this.sftp.read(this.handle, buffer, 0, buffer.length, this.position, (err, read) => {
if (err) {
reject(err)
return
}
this.position += read
resolve(buffer.slice(0, read))
})
if (!wait) {
break
}
}
}))
}
async write (chunk: Uint8Array): Promise<void> {
if (!this.inner) {
throw new Error('File handle is closed')
}
await this.inner.writeAll(chunk)
write (chunk: Buffer): Promise<void> {
return wrapPromise(this.zone, new Promise<void>((resolve, reject) => {
while (true) {
const wait = this.sftp.write(this.handle, chunk, 0, chunk.length, this.position, err => {
if (err) {
reject(err)
return
}
this.position += chunk.length
resolve()
})
if (!wait) {
break
}
}
}))
}
async close (): Promise<void> {
await this.inner?.shutdown()
this.inner = null
close (): Promise<void> {
return wrapPromise(this.zone, promisify(this.sftp.close.bind(this.sftp))(this.handle))
}
}
export class SFTPSession {
get closed$ (): Observable<void> { return this.closed }
private closed = new Subject<void>()
private zone: NgZone
private logger: Logger
constructor (private sftp: russh.SFTP, injector: Injector) {
constructor (private sftp: SFTPWrapper, injector: Injector) {
this.zone = injector.get(NgZone)
this.logger = injector.get(LogService).create('sftp')
sftp.closed$.subscribe(() => {
sftp.on('close', () => {
this.closed.next()
this.closed.complete()
})
@@ -57,64 +86,67 @@ export class SFTPSession {
async readdir (p: string): Promise<SFTPFile[]> {
this.logger.debug('readdir', p)
const entries = await this.sftp.readDirectory(p)
const entries = await wrapPromise(this.zone, promisify<FileEntry[]>(f => this.sftp.readdir(p, f))())
return entries.map(entry => this._makeFile(
posixPath.join(p, entry.name), entry,
posixPath.join(p, entry.filename), entry,
))
}
readlink (p: string): Promise<string> {
this.logger.debug('readlink', p)
return this.sftp.readlink(p)
return wrapPromise(this.zone, promisify<string>(f => this.sftp.readlink(p, f))())
}
async stat (p: string): Promise<SFTPFile> {
this.logger.debug('stat', p)
const stats = await this.sftp.stat(p)
const stats = await wrapPromise(this.zone, promisify<Stats>(f => this.sftp.stat(p, f))())
return {
name: posixPath.basename(p),
fullPath: p,
isDirectory: stats.type === russh.SFTPFileType.Directory,
isSymlink: stats.type === russh.SFTPFileType.Symlink,
mode: stats.permissions ?? 0,
isDirectory: stats.isDirectory(),
isSymlink: stats.isSymbolicLink(),
mode: stats.mode,
size: stats.size,
modified: new Date((stats.mtime ?? 0) * 1000),
modified: new Date(stats.mtime * 1000),
}
}
async open (p: string, mode: number): Promise<SFTPFileHandle> {
this.logger.debug('open', p, mode)
const handle = await this.sftp.open(p, mode)
return new SFTPFileHandle(handle)
async open (p: string, mode: string): Promise<SFTPFileHandle> {
this.logger.debug('open', p)
const handle = await wrapPromise(this.zone, promisify<Buffer>(f => this.sftp.open(p, mode, f))())
return new SFTPFileHandle(this.sftp, handle, this.zone)
}
async rmdir (p: string): Promise<void> {
await this.sftp.removeDirectory(p)
this.logger.debug('rmdir', p)
await promisify((f: any) => this.sftp.rmdir(p, f))()
}
async mkdir (p: string): Promise<void> {
await this.sftp.createDirectory(p)
this.logger.debug('mkdir', p)
await promisify((f: any) => this.sftp.mkdir(p, f))()
}
async rename (oldPath: string, newPath: string): Promise<void> {
this.logger.debug('rename', oldPath, newPath)
await this.sftp.rename(oldPath, newPath)
await promisify((f: any) => this.sftp.rename(oldPath, newPath, f))()
}
async unlink (p: string): Promise<void> {
await this.sftp.removeFile(p)
this.logger.debug('unlink', p)
await promisify((f: any) => this.sftp.unlink(p, f))()
}
async chmod (p: string, mode: string|number): Promise<void> {
this.logger.debug('chmod', p, mode)
await this.sftp.chmod(p, mode)
await promisify((f: any) => this.sftp.chmod(p, mode, f))()
}
async upload (path: string, transfer: FileUpload): Promise<void> {
this.logger.info('Uploading into', path)
const tempPath = path + '.tabby-upload'
try {
const handle = await this.open(tempPath, russh.OPEN_WRITE | russh.OPEN_CREATE)
const handle = await this.open(tempPath, 'w')
while (true) {
const chunk = await transfer.read()
if (!chunk.length) {
@@ -122,13 +154,15 @@ export class SFTPSession {
}
await handle.write(chunk)
}
await handle.close()
await this.unlink(path).catch(() => null)
handle.close()
try {
await this.unlink(path)
} catch { }
await this.rename(tempPath, path)
transfer.close()
} catch (e) {
transfer.cancel()
this.unlink(tempPath).catch(() => null)
this.unlink(tempPath)
throw e
}
}
@@ -136,7 +170,7 @@ export class SFTPSession {
async download (path: string, transfer: FileDownload): Promise<void> {
this.logger.info('Downloading', path)
try {
const handle = await this.open(path, russh.OPEN_READ)
const handle = await this.open(path, 'r')
while (true) {
const chunk = await handle.read()
if (!chunk.length) {
@@ -152,15 +186,15 @@ export class SFTPSession {
}
}
private _makeFile (p: string, entry: russh.SFTPDirectoryEntry): SFTPFile {
private _makeFile (p: string, entry: FileEntry): SFTPFile {
return {
fullPath: p,
name: posixPath.basename(p),
isDirectory: entry.metadata.type === russh.SFTPFileType.Directory,
isSymlink: entry.metadata.type === russh.SFTPFileType.Symlink,
mode: entry.metadata.permissions ?? 0,
size: entry.metadata.size,
modified: new Date((entry.metadata.mtime ?? 0) * 1000),
isDirectory: (entry.attrs.mode & C.S_IFDIR) === C.S_IFDIR,
isSymlink: (entry.attrs.mode & C.S_IFLNK) === C.S_IFLNK,
mode: entry.attrs.mode,
size: entry.attrs.size,
modified: new Date(entry.attrs.mtime * 1000),
}
}
}

View File

@@ -1,15 +1,15 @@
import { Observable, Subject } from 'rxjs'
import stripAnsi from 'strip-ansi'
import { ClientChannel } from 'ssh2'
import { Injector } from '@angular/core'
import { LogService } from 'tabby-core'
import { BaseSession, UTF8SplitterMiddleware, InputProcessor } from 'tabby-terminal'
import { SSHSession } from './ssh'
import { SSHProfile } from '../api'
import * as russh from 'russh'
export class SSHShellSession extends BaseSession {
shell?: russh.Channel
shell?: ClientChannel
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
private serviceMessage = new Subject<string>()
private ssh: SSHSession|null
@@ -53,11 +53,19 @@ export class SSHShellSession extends BaseSession {
this.loginScriptProcessor?.executeUnconditionalScripts()
this.shell.data$.subscribe(data => {
this.emitOutput(Buffer.from(data))
this.shell.on('greeting', greeting => {
this.emitServiceMessage(`Shell greeting: ${greeting}`)
})
this.shell.eof$.subscribe(() => {
this.shell.on('banner', banner => {
this.emitServiceMessage(`Shell banner: ${banner}`)
})
this.shell.on('data', data => {
this.emitOutput(data)
})
this.shell.on('end', () => {
this.logger.info('Shell session ended')
if (this.open) {
this.destroy()
@@ -71,22 +79,19 @@ export class SSHShellSession extends BaseSession {
}
resize (columns: number, rows: number): void {
this.shell?.resizePTY({
columns,
rows,
pixHeight: 0,
pixWidth: 0,
})
if (this.shell) {
this.shell.setWindow(rows, columns, rows, columns)
}
}
write (data: Buffer): void {
if (this.shell) {
this.shell.write(new Uint8Array(data))
this.shell.write(data)
}
}
kill (_signal?: string): void {
// this.shell?.signal(signal ?? 'TERM')
kill (signal?: string): void {
this.shell?.signal(signal ?? 'TERM')
}
async destroy (): Promise<void> {

View File

@@ -1,22 +1,24 @@
import * as fs from 'mz/fs'
import * as crypto from 'crypto'
import * as sshpk from 'sshpk'
import colors from 'ansi-colors'
import stripAnsi from 'strip-ansi'
import * as shellQuote from 'shell-quote'
import { Injector } from '@angular/core'
import { Injector, NgZone } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, FileProvidersService, NotificationsService, PromptModalComponent, LogService, Logger, TranslateService, Platform, HostAppService } from 'tabby-core'
import { ConfigService, FileProvidersService, HostAppService, NotificationsService, Platform, PlatformService, wrapPromise, PromptModalComponent, LogService, Logger, TranslateService } from 'tabby-core'
import { Socket } from 'net'
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
import { Subject, Observable } from 'rxjs'
import { HostKeyPromptModalComponent } from '../components/hostKeyPromptModal.component'
import { HTTPProxyStream, ProxyCommandStream, SocksProxyStream } from '../services/ssh.service'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHKnownHostsService } from '../services/sshKnownHosts.service'
import { promisify } from 'util'
import { SFTPSession } from './sftp'
import { SSHAlgorithmType, SSHProfile, AutoPrivateKeyLocator, PortForwardType } from '../api'
import { SSHAlgorithmType, PortForwardType, SSHProfile, SSHProxyStream, AutoPrivateKeyLocator } from '../api'
import { ForwardedPort } from './forwards'
import { X11Socket } from './x11'
import { supportedAlgorithms } from '../algorithms'
import * as russh from 'russh'
const WINDOWS_OPENSSH_AGENT_PIPE = '\\\\.\\pipe\\openssh-ssh-agent'
@@ -25,74 +27,48 @@ export interface Prompt {
echo?: boolean
}
type AuthMethod = {
type: 'none'|'prompt-password'|'hostbased'
} | {
type: 'keyboard-interactive',
savedPassword?: string
} | {
type: 'saved-password',
password: string
} | {
type: 'publickey'
name: string
contents: Buffer
} | {
type: 'agent',
kind: 'unix-socket',
path: string
} | {
type: 'agent',
kind: 'named-pipe',
path: string
} | {
type: 'agent',
kind: 'pageant',
interface AuthMethod {
type: 'none'|'publickey'|'agent'|'password'|'keyboard-interactive'|'hostbased'
name?: string
contents?: Buffer
}
interface Handshake {
kex: string
serverHostKey: string
}
export class KeyboardInteractivePrompt {
readonly responses: string[] = []
private _resolve: (value: string[]) => void
private _reject: (reason: any) => void
readonly promise = new Promise<string[]>((resolve, reject) => {
this._resolve = resolve
this._reject = reject
})
responses: string[] = []
constructor (
public name: string,
public instruction: string,
public prompts: Prompt[],
private callback: (_: string[]) => void,
) {
this.responses = new Array(this.prompts.length).fill('')
}
isAPasswordPrompt (index: number): boolean {
return this.prompts[index].prompt.toLowerCase().includes('password') && !this.prompts[index].echo
}
respond (): void {
this._resolve(this.responses)
}
reject (): void {
this._reject(new Error('Keyboard-interactive auth rejected'))
this.callback(this.responses)
}
}
export class SSHSession {
shell?: russh.Channel
ssh: russh.SSHClient|russh.AuthenticatedSSHClient
sftp?: russh.SFTP
shell?: ClientChannel
ssh: Client
sftp?: SFTPWrapper
forwardedPorts: ForwardedPort[] = []
jumpChannel: russh.Channel|null = null
jumpStream: any
proxyCommandStream: SSHProxyStream|null = null
savedPassword?: string
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt }
get willDestroy$ (): Observable<void> { return this.willDestroy }
activePrivateKey: russh.KeyPair|null = null
agentPath?: string
activePrivateKey: string|null = null
authUsername: string|null = null
open = false
@@ -103,11 +79,15 @@ export class SSHSession {
private serviceMessage = new Subject<string>()
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
private willDestroy = new Subject<void>()
private keychainPasswordUsed = false
private hostKeyDigest = ''
private passwordStorage: PasswordStorageService
private ngbModal: NgbModal
private hostApp: HostAppService
private platform: PlatformService
private notifications: NotificationsService
private zone: NgZone
private fileProviders: FileProvidersService
private config: ConfigService
private translate: TranslateService
@@ -123,7 +103,9 @@ export class SSHSession {
this.passwordStorage = injector.get(PasswordStorageService)
this.ngbModal = injector.get(NgbModal)
this.hostApp = injector.get(HostAppService)
this.platform = injector.get(PlatformService)
this.notifications = injector.get(NotificationsService)
this.zone = injector.get(NgZone)
this.fileProviders = injector.get(FileProvidersService)
this.config = injector.get(ConfigService)
this.translate = injector.get(TranslateService)
@@ -138,6 +120,27 @@ export class SSHSession {
}
async init (): Promise<void> {
if (this.hostApp.platform === Platform.Windows) {
if (this.config.store.ssh.agentType === 'auto') {
if (await fs.exists(WINDOWS_OPENSSH_AGENT_PIPE)) {
this.agentPath = WINDOWS_OPENSSH_AGENT_PIPE
} else {
if (
await this.platform.isProcessRunning('pageant.exe') ||
await this.platform.isProcessRunning('gpg-agent.exe')
) {
this.agentPath = 'pageant'
}
}
} else if (this.config.store.ssh.agentType === 'pageant') {
this.agentPath = 'pageant'
} else {
this.agentPath = this.config.store.ssh.agentPath || WINDOWS_OPENSSH_AGENT_PIPE
}
} else {
this.agentPath = process.env.SSH_AUTH_SOCK!
}
this.remainingAuthMethods = [{ type: 'none' }]
if (!this.profile.options.auth || this.profile.options.auth === 'publicKey') {
if (this.profile.options.privateKeys?.length) {
@@ -164,192 +167,183 @@ export class SSHSession {
}
}
}
if (!this.profile.options.auth || this.profile.options.auth === 'agent') {
const spec = await this.getAgentConnectionSpec()
if (!spec) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running Agent process is found`)
if (!this.agentPath) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running agent is detected`)
} else {
this.remainingAuthMethods.push({
type: 'agent',
...spec,
})
this.remainingAuthMethods.push({ type: 'agent' })
}
}
if (!this.profile.options.auth || this.profile.options.auth === 'password') {
if (this.profile.options.password) {
this.remainingAuthMethods.push({ type: 'saved-password', password: this.profile.options.password })
}
const password = await this.passwordStorage.loadPassword(this.profile)
if (password) {
this.remainingAuthMethods.push({ type: 'saved-password', password })
}
this.remainingAuthMethods.push({ type: 'prompt-password' })
this.remainingAuthMethods.push({ type: 'password' })
}
if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') {
const savedPassword = this.profile.options.password ?? await this.passwordStorage.loadPassword(this.profile)
if (savedPassword) {
this.remainingAuthMethods.push({ type: 'keyboard-interactive', savedPassword })
}
this.remainingAuthMethods.push({ type: 'keyboard-interactive' })
}
this.remainingAuthMethods.push({ type: 'hostbased' })
}
private async getAgentConnectionSpec (): Promise<russh.AgentConnectionSpec|null> {
if (this.hostApp.platform === Platform.Windows) {
if (this.config.store.ssh.agentType === 'auto') {
if (await fs.exists(WINDOWS_OPENSSH_AGENT_PIPE)) {
return {
kind: 'named-pipe',
path: WINDOWS_OPENSSH_AGENT_PIPE,
}
} else if (russh.isPageantRunning()) {
return {
kind: 'pageant',
}
} else {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running Agent process is found`)
}
} else if (this.config.store.ssh.agentType === 'pageant') {
return {
kind: 'pageant',
}
} else {
return {
kind: 'named-pipe',
path: this.config.store.ssh.agentPath || WINDOWS_OPENSSH_AGENT_PIPE,
}
}
} else {
return {
kind: 'unix-socket',
path: process.env.SSH_AUTH_SOCK!,
}
}
return null
}
async openSFTP (): Promise<SFTPSession> {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open SFTP session before auth')
}
if (!this.sftp) {
this.sftp = await this.ssh.openSFTPChannel()
this.sftp = await wrapPromise(this.zone, promisify<SFTPWrapper>(f => this.ssh.sftp(f))())
}
return new SFTPSession(this.sftp, this.injector)
}
async start (): Promise<void> {
const log = (s: any) => this.emitServiceMessage(s)
const ssh = new Client()
this.ssh = ssh
await this.init()
let connected = false
const algorithms = {}
for (const key of Object.values(SSHAlgorithmType)) {
algorithms[key] = this.profile.options.algorithms![key].filter(x => supportedAlgorithms[key].includes(x))
}
// eslint-disable-next-line @typescript-eslint/init-declarations
let transport: russh.SshTransport
if (this.profile.options.proxyCommand) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy command ') + ` Using ${this.profile.options.proxyCommand}`)
const argv = shellQuote.parse(this.profile.options.proxyCommand)
transport = await russh.SshTransport.newCommand(argv[0], argv.slice(1))
} else if (this.jumpChannel) {
transport = await russh.SshTransport.newSshChannel(await this.jumpChannel.take())
this.jumpChannel = null
} else if (this.profile.options.socksProxyHost) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ` Using ${this.profile.options.socksProxyHost}:${this.profile.options.socksProxyPort}`)
transport = await russh.SshTransport.newSocksProxy(
this.profile.options.socksProxyHost,
this.profile.options.socksProxyPort ?? 1080,
this.profile.options.host,
this.profile.options.port ?? 22,
)
} else if (this.profile.options.httpProxyHost) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ` Using ${this.profile.options.httpProxyHost}:${this.profile.options.httpProxyPort}`)
transport = await russh.SshTransport.newHttpProxy(
this.profile.options.httpProxyHost,
this.profile.options.httpProxyPort ?? 8080,
this.profile.options.host,
this.profile.options.port ?? 22,
)
} else {
transport = await russh.SshTransport.newSocket(`${this.profile.options.host.trim()}:${this.profile.options.port ?? 22}`)
}
this.ssh = await russh.SSHClient.connect(
transport,
async key => {
if (!await this.verifyHostKey(key)) {
return false
const hostVerifiedPromise: Promise<void> = new Promise((resolve, reject) => {
ssh.on('handshake', async handshake => {
if (!await this.verifyHostKey(handshake)) {
this.ssh.end()
reject(new Error('Host key verification failed'))
}
this.logger.info('Host key verified')
return true
},
{
preferred: {
ciphers: this.profile.options.algorithms?.[SSHAlgorithmType.CIPHER]?.filter(x => supportedAlgorithms[SSHAlgorithmType.CIPHER].includes(x)),
kex: this.profile.options.algorithms?.[SSHAlgorithmType.KEX]?.filter(x => supportedAlgorithms[SSHAlgorithmType.KEX].includes(x)),
mac: this.profile.options.algorithms?.[SSHAlgorithmType.HMAC]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HMAC].includes(x)),
key: this.profile.options.algorithms?.[SSHAlgorithmType.HOSTKEY]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HOSTKEY].includes(x)),
},
keepaliveIntervalSeconds: Math.round((this.profile.options.keepaliveInterval ?? 15000) / 1000),
this.logger.info('Handshake complete:', handshake)
resolve()
})
})
const resultPromise: Promise<void> = new Promise(async (resolve, reject) => {
ssh.on('ready', () => {
connected = true
// Fix SSH Lagging
ssh.setNoDelay(true)
if (this.savedPassword) {
this.passwordStorage.savePassword(this.profile, this.savedPassword)
}
this.zone.run(resolve)
})
ssh.on('error', error => {
if (error.message === 'All configured authentication methods failed') {
this.passwordStorage.deletePassword(this.profile)
}
this.zone.run(() => {
if (connected) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.notifications.error(error.toString())
} else {
reject(error)
}
})
})
ssh.on('close', () => {
if (this.open) {
this.destroy()
}
})
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
this.emitKeyboardInteractivePrompt(new KeyboardInteractivePrompt(
name,
instructions,
prompts,
finish,
))
}))
ssh.on('greeting', greeting => {
if (!this.profile.options.skipBanner) {
log('Greeting: ' + greeting)
}
})
ssh.on('banner', banner => {
if (!this.profile.options.skipBanner) {
log(banner)
}
})
})
try {
if (this.profile.options.socksProxyHost) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ` Using ${this.profile.options.socksProxyHost}:${this.profile.options.socksProxyPort}`)
this.proxyCommandStream = new SocksProxyStream(this.profile)
}
if (this.profile.options.httpProxyHost) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ` Using ${this.profile.options.httpProxyHost}:${this.profile.options.httpProxyPort}`)
this.proxyCommandStream = new HTTPProxyStream(this.profile)
}
if (this.profile.options.proxyCommand) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy command ') + ` Using ${this.profile.options.proxyCommand}`)
this.proxyCommandStream = new ProxyCommandStream(this.profile.options.proxyCommand)
}
if (this.proxyCommandStream) {
this.proxyCommandStream.destroyed$.subscribe(err => {
if (err) {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` ${err.message}`)
this.destroy()
}
})
this.proxyCommandStream.message$.subscribe(message => {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ' ' + message.trim())
})
await this.proxyCommandStream.start()
}
this.authUsername ??= this.profile.options.user
if (!this.authUsername) {
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = `Username for ${this.profile.options.host}`
try {
const result = await modal.result.catch(() => null)
this.authUsername = result?.value ?? null
} catch {
this.authUsername = 'root'
}
}
if (this.authUsername?.startsWith('$')) {
try {
const result = process.env[this.authUsername.slice(1)]
this.authUsername = result ?? this.authUsername
} catch {
this.authUsername = 'root'
}
}
ssh.connect({
host: this.profile.options.host.trim(),
port: this.profile.options.port ?? 22,
sock: this.proxyCommandStream?.socket ?? this.jumpStream,
username: this.authUsername ?? undefined,
tryKeyboard: true,
agent: this.agentPath,
agentForward: this.profile.options.agentForward && !!this.agentPath,
keepaliveInterval: this.profile.options.keepaliveInterval ?? 15000,
keepaliveCountMax: this.profile.options.keepaliveCountMax,
connectionTimeoutSeconds: this.profile.options.readyTimeout ? Math.round(this.profile.options.readyTimeout / 1000) : undefined,
},
)
this.ssh.banner$.subscribe(banner => {
if (!this.profile.options.skipBanner) {
this.emitServiceMessage(banner)
}
})
this.ssh.disconnect$.subscribe(() => {
if (this.open) {
this.destroy()
}
})
// Authentication
this.authUsername ??= this.profile.options.user
if (!this.authUsername) {
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = `Username for ${this.profile.options.host}`
try {
const result = await modal.result.catch(() => null)
this.authUsername = result?.value ?? null
} catch {
this.authUsername = 'root'
}
readyTimeout: this.profile.options.readyTimeout,
hostVerifier: (key: any) => {
this.hostKeyDigest = crypto.createHash('sha256').update(key).digest('base64')
return true
},
algorithms,
authHandler: (methodsLeft, partialSuccess, callback) => {
this.zone.run(async () => {
callback(await this.handleAuth(methodsLeft))
})
},
})
} catch (e) {
this.notifications.error(e.message)
throw e
}
if (this.authUsername?.startsWith('$')) {
try {
const result = process.env[this.authUsername.slice(1)]
this.authUsername = result ?? this.authUsername
} catch {
this.authUsername = 'root'
}
}
const authenticatedClient = await this.handleAuth()
if (authenticatedClient) {
this.ssh = authenticatedClient
} else {
this.ssh.disconnect()
this.passwordStorage.deletePassword(this.profile)
// eslint-disable-next-line @typescript-eslint/no-base-to-string
throw new Error('Authentication rejected')
}
// auth success
if (this.savedPassword) {
this.passwordStorage.savePassword(this.profile, this.savedPassword)
}
await resultPromise
await hostVerifiedPromise
for (const fw of this.profile.options.forwardedPorts ?? []) {
this.addPortForward(Object.assign(new ForwardedPort(), fw))
@@ -357,11 +351,12 @@ export class SSHSession {
this.open = true
this.ssh.tcpChannelOpen$.subscribe(async event => {
this.logger.info(`Incoming forwarded connection: ${event.clientAddress}:${event.clientPort} -> ${event.targetAddress}:${event.targetPort}`)
const forward = this.forwardedPorts.find(x => x.port === event.targetPort && x.host === event.targetAddress)
this.ssh.on('tcp connection', (details, accept, reject) => {
this.logger.info(`Incoming forwarded connection: (remote) ${details.srcIP}:${details.srcPort} -> (local) ${details.destIP}:${details.destPort}`)
const forward = this.forwardedPorts.find(x => x.port === details.destPort)
if (!forward) {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Rejected incoming forwarded connection for unrecognized port ${event.targetAddress}:${event.targetPort}`)
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Rejected incoming forwarded connection for unrecognized port ${details.destPort}`)
reject()
return
}
const socket = new Socket()
@@ -369,19 +364,24 @@ export class SSHSession {
socket.on('error', e => {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not forward the remote connection to ${forward.targetAddress}:${forward.targetPort}: ${e}`)
event.channel.close()
reject()
})
event.channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => event.channel.write(Uint8Array.from(data)))
event.channel.closed$.subscribe(() => socket.destroy())
socket.on('close', () => event.channel.close())
socket.on('connect', () => {
this.logger.info('Connection forwarded')
const stream = accept()
stream.pipe(socket)
socket.pipe(stream)
stream.on('close', () => {
socket.destroy()
})
socket.on('close', () => {
stream.close()
})
})
})
this.ssh.x11ChannelOpen$.subscribe(async event => {
this.logger.info(`Incoming X11 connection from ${event.clientAddress}:${event.clientPort}`)
this.ssh.on('x11', async (details, accept, reject) => {
this.logger.info(`Incoming X11 connection from ${details.srcIP}:${details.srcPort}`)
const displaySpec = (this.config.store.ssh.x11Display || process.env.DISPLAY) ?? 'localhost:0'
this.logger.debug(`Trying display ${displaySpec}`)
@@ -389,18 +389,14 @@ export class SSHSession {
try {
const x11Stream = await socket.connect(displaySpec)
this.logger.info('Connection forwarded')
event.channel.data$.subscribe(data => {
x11Stream.write(data)
})
x11Stream.on('data', data => {
event.channel.write(Uint8Array.from(data))
})
event.channel.closed$.subscribe(() => {
const stream = accept()
stream.pipe(x11Stream)
x11Stream.pipe(stream)
stream.on('close', () => {
socket.destroy()
})
x11Stream.on('close', () => {
event.channel.close()
stream.close()
})
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -411,43 +407,27 @@ export class SSHSession {
this.emitServiceMessage(' * VcXsrv: https://sourceforge.net/projects/vcxsrv/')
this.emitServiceMessage(' * Xming: https://sourceforge.net/projects/xming/')
}
event.channel.close()
reject()
}
})
this.ssh.agentChannelOpen$.subscribe(async channel => {
const spec = await this.getAgentConnectionSpec()
if (!spec) {
await channel.close()
return
}
const agent = await russh.SSHAgentStream.connect(spec)
channel.data$.subscribe(data => agent.write(data))
agent.data$.subscribe(data => channel.write(data), undefined, () => channel.close())
channel.closed$.subscribe(() => agent.close())
})
}
private async verifyHostKey (key: russh.SshPublicKey): Promise<boolean> {
private async verifyHostKey (handshake: Handshake): Promise<boolean> {
this.emitServiceMessage('Host key fingerprint:')
this.emitServiceMessage(colors.white.bgBlack(` ${key.algorithm()} `) + colors.bgBlackBright(' ' + key.fingerprint() + ' '))
this.emitServiceMessage(colors.white.bgBlack(` ${handshake.serverHostKey} `) + colors.bgBlackBright(' ' + this.hostKeyDigest + ' '))
if (!this.config.store.ssh.verifyHostKeys) {
return true
}
const selector = {
host: this.profile.options.host,
port: this.profile.options.port ?? 22,
type: key.algorithm(),
type: handshake.serverHostKey,
}
const keyDigest = crypto.createHash('sha256').update(key.bytes()).digest('base64')
const knownHost = this.knownHosts.getFor(selector)
if (!knownHost || knownHost.digest !== keyDigest) {
if (!knownHost || knownHost.digest !== this.hostKeyDigest) {
const modal = this.ngbModal.open(HostKeyPromptModalComponent)
modal.componentInstance.selector = selector
modal.componentInstance.digest = keyDigest
modal.componentInstance.digest = this.hostKeyDigest
return modal.result.catch(() => false)
}
return true
@@ -469,49 +449,57 @@ export class SSHSession {
this.keyboardInteractivePrompt.next(prompt)
}
async handleAuth (methodsLeft?: string[] | null): Promise<russh.AuthenticatedSSHClient|null> {
async handleAuth (methodsLeft?: string[] | null): Promise<any> {
this.activePrivateKey = null
if (!(this.ssh instanceof russh.SSHClient)) {
throw new Error('Wrong state for auth handling')
}
if (!this.authUsername) {
throw new Error('No username')
}
while (true) {
const method = this.remainingAuthMethods.shift()
if (!method) {
return null
return false
}
if (methodsLeft && !methodsLeft.includes(method.type) && method.type !== 'agent') {
// Agent can still be used even if not in methodsLeft
this.logger.info('Server does not support auth method', method.type)
continue
}
if (method.type === 'saved-password') {
this.emitServiceMessage(this.translate.instant('Using saved password'))
const result = await this.ssh.authenticateWithPassword(this.authUsername, method.password)
if (result) {
return result
if (method.type === 'password') {
if (this.profile.options.password) {
this.emitServiceMessage(this.translate.instant('Using preset password'))
return {
type: 'password',
username: this.authUsername,
password: this.profile.options.password,
}
}
}
if (method.type === 'prompt-password') {
if (!this.keychainPasswordUsed && this.profile.options.user) {
const password = await this.passwordStorage.loadPassword(this.profile)
if (password) {
this.emitServiceMessage(this.translate.instant('Trying saved password'))
this.keychainPasswordUsed = true
return {
type: 'password',
username: this.authUsername,
password,
}
}
}
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = `Password for ${this.authUsername}@${this.profile.options.host}`
modal.componentInstance.password = true
modal.componentInstance.showRememberCheckbox = true
try {
const promptResult = await modal.result.catch(() => null)
if (promptResult) {
if (promptResult.remember) {
this.savedPassword = promptResult.value
const result = await modal.result.catch(() => null)
if (result) {
if (result.remember) {
this.savedPassword = result.value
}
const result = await this.ssh.authenticateWithPassword(this.authUsername, promptResult.value)
if (result) {
return result
return {
type: 'password',
username: this.authUsername,
password: result.value,
}
} else {
continue
@@ -520,104 +508,50 @@ export class SSHSession {
continue
}
}
if (method.type === 'publickey') {
if (method.type === 'publickey' && method.contents) {
try {
const key = await this.loadPrivateKey(method.name, method.contents)
const result = await this.ssh.authenticateWithKeyPair(this.authUsername, key)
if (result) {
return result
const key = await this.loadPrivateKey(method.name!, method.contents)
return {
type: 'publickey',
username: this.authUsername,
key,
}
} catch (e) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.name}: ${e}`)
continue
}
}
if (method.type === 'keyboard-interactive') {
let state: russh.AuthenticatedSSHClient|russh.KeyboardInteractiveAuthenticationState = await this.ssh.startKeyboardInteractiveAuthentication(this.authUsername)
while (true) {
if (state.state === 'failure') {
break
}
const prompts = state.prompts()
let responses: string[] = []
// OpenSSH can send a k-i request without prompts
// just respond ok to it
if (prompts.length > 0) {
const prompt = new KeyboardInteractivePrompt(
state.name,
state.instructions,
state.prompts(),
)
if (method.savedPassword) {
// eslint-disable-next-line max-depth
for (let i = 0; i < prompt.prompts.length; i++) {
// eslint-disable-next-line max-depth
if (prompt.isAPasswordPrompt(i)) {
prompt.responses[i] = method.savedPassword
}
}
}
this.emitKeyboardInteractivePrompt(prompt)
try {
// eslint-disable-next-line @typescript-eslint/await-thenable
responses = await prompt.promise
} catch {
break // this loop
}
}
state = await this.ssh .continueKeyboardInteractiveAuthentication(responses)
if (state instanceof russh.AuthenticatedSSHClient) {
return state
}
}
}
if (method.type === 'agent') {
try {
const result = await this.ssh.authenticateWithAgent(this.authUsername, method)
if (result) {
return result
}
} catch (e) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to authenticate using agent: ${e}`)
continue
}
}
return method.type
}
return null
}
async addPortForward (fw: ForwardedPort): Promise<void> {
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) {
await fw.startLocalListener(async (accept, reject, sourceAddress, sourcePort, targetAddress, targetPort) => {
await fw.startLocalListener((accept, reject, sourceAddress, sourcePort, targetAddress, targetPort) => {
this.logger.info(`New connection on ${fw}`)
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
this.logger.error(`Connection while unauthenticated on ${fw}`)
reject()
return
}
const channel = await this.ssh.openTCPForwardChannel({
addressToConnectTo: targetAddress,
portToConnectTo: targetPort,
originatorAddress: sourceAddress ?? '127.0.0.1',
originatorPort: sourcePort ?? 0,
}).catch(err => {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
reject()
throw err
})
const socket = accept()
channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => channel.write(Uint8Array.from(data)))
channel.closed$.subscribe(() => socket.destroy())
socket.on('close', () => channel.close())
this.ssh.forwardOut(
sourceAddress ?? '127.0.0.1',
sourcePort ?? 0,
targetAddress,
targetPort,
(err, stream) => {
if (err) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
reject()
return
}
const socket = accept()
stream.pipe(socket)
socket.pipe(stream)
stream.on('close', () => {
socket.destroy()
})
socket.on('close', () => {
stream.close()
})
},
)
}).then(() => {
this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwarded ${fw}`)
this.forwardedPorts.push(fw)
@@ -627,16 +561,17 @@ export class SSHSession {
})
}
if (fw.type === PortForwardType.Remote) {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot add remote port forward before auth')
}
try {
await this.ssh.forwardTCPPort(fw.host, fw.port)
} catch (err) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected port forwarding for ${fw}: ${err}`)
return
}
await new Promise<void>((resolve, reject) => {
this.ssh.forwardIn(fw.host, fw.port, err => {
if (err) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected port forwarding for ${fw}: ${err}`)
reject(err)
return
}
resolve()
})
})
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwarded ${fw}`)
this.forwardedPorts.push(fw)
}
@@ -648,10 +583,7 @@ export class SSHSession {
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
}
if (fw.type === PortForwardType.Remote) {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot remove remote port forward before auth')
}
this.ssh.stopForwardingTCPPort(fw.host, fw.port)
this.ssh.unforwardIn(fw.host, fw.port)
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
}
this.emitServiceMessage(`Stopped forwarding ${fw}`)
@@ -662,55 +594,43 @@ export class SSHSession {
this.willDestroy.next()
this.willDestroy.complete()
this.serviceMessage.complete()
this.ssh.disconnect()
this.proxyCommandStream?.stop()
this.ssh.end()
}
async openShellChannel (options: { x11: boolean }): Promise<russh.Channel> {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open shell channel before auth')
}
const ch = await this.ssh.openSessionChannel()
await ch.requestPTY('xterm-256color', {
columns: 80,
rows: 24,
pixHeight: 0,
pixWidth: 0,
})
if (options.x11) {
await ch.requestX11Forwarding({
singleConnection: false,
authProtocol: 'MIT-MAGIC-COOKIE-1',
authCookie: crypto.randomBytes(16).toString('hex'),
screenNumber: 0,
openShellChannel (options: { x11: boolean }): Promise<ClientChannel> {
return new Promise<ClientChannel>((resolve, reject) => {
this.ssh.shell({ term: 'xterm-256color' }, options, (err, shell) => {
if (err) {
reject(err)
} else {
resolve(shell)
}
})
}
if (this.profile.options.agentForward) {
await ch.requestAgentForwarding()
}
await ch.requestShell()
return ch
})
}
async loadPrivateKey (name: string, privateKeyContents: Buffer): Promise<russh.KeyPair> {
async loadPrivateKey (name: string, privateKeyContents: Buffer): Promise<string|null> {
this.emitServiceMessage(`Loading private key: ${name}`)
this.activePrivateKey = await this.loadPrivateKeyWithPassphraseMaybe(privateKeyContents.toString())
const parsedKey = await this.parsePrivateKey(privateKeyContents.toString())
this.activePrivateKey = parsedKey.toString('openssh')
return this.activePrivateKey
}
async loadPrivateKeyWithPassphraseMaybe (privateKey: string): Promise<russh.KeyPair> {
async parsePrivateKey (privateKey: string): Promise<any> {
const keyHash = crypto.createHash('sha512').update(privateKey).digest('hex')
let triedSavedPassphrase = false
let passphrase: string|null = null
while (true) {
try {
return await russh.KeyPair.parse(privateKey, passphrase ?? undefined)
return sshpk.parsePrivateKey(privateKey, 'auto', { passphrase })
} catch (e) {
if (!triedSavedPassphrase) {
passphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash)
triedSavedPassphrase = true
continue
}
if (e.toString() === 'Error: Keys(KeyIsEncrypted)' || e.toString() === 'Error: Keys(SshKey(Crypto))') {
if (e instanceof sshpk.KeyEncryptedError || e instanceof sshpk.KeyParseError) {
await this.passwordStorage.deletePrivateKeyPassword(keyHash)
const modal = this.ngbModal.open(PromptModalComponent)

View File

@@ -7,4 +7,9 @@ import config from '../webpack.plugin.config.mjs'
export default () => config({
name: 'ssh',
dirname: __dirname,
alias: {
'cpu-features': false,
'./crypto/build/Release/sshcrypto.node': false,
'../build/Release/cpufeatures.node': false,
},
})

View File

@@ -9,11 +9,33 @@
dependencies:
ipv6 "*"
"@types/node@*":
version "22.1.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.1.0.tgz#6d6adc648b5e03f0e83c78dc788c2b037d0ad94b"
integrity sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==
dependencies:
undici-types "~6.13.0"
"@types/node@20.3.1":
version "20.3.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe"
integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==
"@types/ssh2-streams@*":
version "0.1.12"
resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz#e68795ba2bf01c76b93f9c9809e1f42f0eaaec5f"
integrity sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==
dependencies:
"@types/node" "*"
"@types/ssh2@^0.5.46":
version "0.5.52"
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.52.tgz#9dbd8084e2a976e551d5e5e70b978ed8b5965741"
integrity sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==
dependencies:
"@types/node" "*"
"@types/ssh2-streams" "*"
ansi-colors@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
@@ -24,16 +46,40 @@ ansi-regex@^6.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
dependencies:
safer-buffer "~2.1.0"
assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
async@0.2.x:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
integrity sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==
integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
dependencies:
tweetnacl "^0.14.3"
bn.js@^4.0.0, bn.js@^4.1.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -42,17 +88,22 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brorand@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==
cli@0.4.x:
version "0.4.5"
resolved "https://registry.yarnpkg.com/cli/-/cli-0.4.5.tgz#78f9485cd161b566e9a6c72d7170c4270e81db61"
integrity sha512-dbn5HyeJWSOU58RwOEiF1VWrl7HRvDsKLpu0uiI/vExH6iNoyUzjB5Mr3IJY5DVUfnbpe9793xw4DFJVzC9nWQ==
integrity sha1-ePlIXNFhtWbppsctcXDEJw6B22E=
dependencies:
glob ">= 3.1.4"
cliff@0.1.x:
version "0.1.10"
resolved "https://registry.yarnpkg.com/cliff/-/cliff-0.1.10.tgz#53be33ea9f59bec85609ee300ac4207603e52013"
integrity sha512-roZWcC2Cxo/kKjRXw7YUpVNtxJccbvcl7VzTjUYgLQk6Ot0R8bm2netbhSZYWWNrKlOO/7HD6GXHl8dtzE6SiQ==
integrity sha1-U74z6p9ZvshWCe4wCsQgdgPlIBM=
dependencies:
colors "~1.0.3"
eyes "~0.1.8"
@@ -61,12 +112,12 @@ cliff@0.1.x:
colors@0.6.x:
version "0.6.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
integrity sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw==
integrity sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=
colors@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
concat-map@0.0.1:
version "0.0.1"
@@ -76,19 +127,62 @@ concat-map@0.0.1:
cycle@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
integrity sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==
integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI=
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
dependencies:
assert-plus "^1.0.0"
diffie-hellman@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
dependencies:
bn.js "^4.1.0"
miller-rabin "^4.0.0"
randombytes "^2.0.0"
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
eyes@0.1.x, eyes@~0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==
integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
glob@7.2.3, "glob@>= 3.1.4", glob@^7.1.3:
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
dependencies:
assert-plus "^1.0.0"
"glob@>= 3.1.4":
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -116,7 +210,7 @@ inherits@2:
ipv6@*:
version "3.1.3"
resolved "https://registry.yarnpkg.com/ipv6/-/ipv6-3.1.3.tgz#4d9064f9c2dafa0dd10b8b7d76ffca4aad31b3b9"
integrity sha512-TmLbUIURMAZ161GZDddTtAAb3aceRNLn7PRmP8fANp8xDRCW9oIQva8eenA48bRvw347jBqSREXMI38DybbUiQ==
integrity sha1-TZBk+cLa+g3RC4t9dv/KSq0xs7k=
dependencies:
cli "0.4.x"
cliff "0.1.x"
@@ -125,7 +219,27 @@ ipv6@*:
isstream@0.1.x:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==
dependencies:
bn.js "^4.0.0"
brorand "^1.0.1"
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
minimatch@^3.1.1:
version "3.1.2"
@@ -149,7 +263,14 @@ path-is-absolute@^1.0.0:
pkginfo@0.3.x:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
integrity sha512-yO5feByMzAp96LtP58wvPKSbaKAi/1C4kV9XpTctr6EepnP6F33RBNOiVrdz9BrPA98U2BMFsTNHo44TWcbQ2A==
integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=
randombytes@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
rimraf@^3.0.0:
version "3.0.2"
@@ -163,15 +284,39 @@ run-script-os@^1.1.3:
resolved "https://registry.yarnpkg.com/run-script-os/-/run-script-os-1.1.6.tgz#8b0177fb1b54c99a670f95c7fdc54f18b9c72347"
integrity sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==
safe-buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sprintf@0.1.x:
version "0.1.5"
resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.5.tgz#8f83e39a9317c1a502cb7db8050e51c679f6edcf"
integrity sha512-4X5KsuXFQ7f+d7Y+bi4qSb6eI+YoifDTGr0MQJXRoYO7BO7evfRCjds6kk3z7l5CiJYxgDN1x5Er4WiyCt+zTQ==
integrity sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=
sshpk@Eugeny/node-sshpk#c2b71d1243714d2daf0988f84c3323d180817136:
version "1.18.0"
resolved "https://codeload.github.com/Eugeny/node-sshpk/tar.gz/c2b71d1243714d2daf0988f84c3323d180817136"
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
bcrypt-pbkdf "^1.0.0"
dashdash "^1.12.0"
ecc-jsbn "~0.1.1"
getpass "^0.1.1"
jsbn "~0.1.0"
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
strip-ansi@^7.0.0:
version "7.1.0"
@@ -194,10 +339,20 @@ tmp@^0.2.0:
dependencies:
rimraf "^3.0.0"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
undici-types@~6.13.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.13.0.tgz#e3e79220ab8c81ed1496b5812471afd7cf075ea5"
integrity sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==
winston@0.8.x:
version "0.8.3"
resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0"
integrity sha512-fPoamsHq8leJ62D1M9V/f15mjQ1UHe4+7j1wpAT3fqgA5JqhJkk4aIfPEjfMTI9x6ZTjaLOpMAjluLtmgO5b6g==
integrity sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=
dependencies:
async "0.2.x"
colors "0.6.x"

View File

@@ -1,13 +1,13 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@14.14.31":
version "14.14.31"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
ansi-colors@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@14.14.31":
version "14.14.31"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
ansi-colors@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==

View File

@@ -13,7 +13,6 @@ import { ResizeEvent, BaseTerminalProfile } from './interfaces'
import { TerminalDecorator } from './decorator'
import { SearchPanelComponent } from '../components/searchPanel.component'
import { MultifocusService } from '../services/multifocus.service'
import { getTerminalBackgroundColor } from '../helpers'
const INACTIVE_TAB_UNLOAD_DELAY = 1000 * 30
@@ -576,7 +575,14 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
configure (): void {
this.frontend?.configure(this.profile)
this.backgroundColor = getTerminalBackgroundColor(this.config, this.themes, this.profile.terminalColorScheme)
if (!this.themes.findCurrentTheme().followsColorScheme && this.config.store.terminal.background === 'colorScheme') {
const scheme = this.profile.terminalColorScheme ?? this.config.store.terminal.colorScheme
if (scheme.background) {
this.backgroundColor = scheme.background
}
} else {
this.backgroundColor = null
}
}
zoomIn (): void {

View File

@@ -1,4 +1,3 @@
import deepEqual from 'deep-equal'
import { BehaviorSubject, filter, firstValueFrom, takeUntil } from 'rxjs'
import { Injector } from '@angular/core'
import { ConfigService, getCSSFontFamily, getWindows10Build, HostAppService, HotkeysService, Platform, PlatformService, ThemesService } from 'tabby-core'
@@ -12,9 +11,9 @@ import { Unicode11Addon } from '@xterm/addon-unicode11'
import { SerializeAddon } from '@xterm/addon-serialize'
import { ImageAddon } from '@xterm/addon-image'
import { CanvasAddon } from '@xterm/addon-canvas'
import { BaseTerminalProfile, TerminalColorScheme } from '../api/interfaces'
import { getTerminalBackgroundColor } from '../helpers'
import './xterm.css'
import deepEqual from 'deep-equal'
import { BaseTerminalProfile, TerminalColorScheme } from '../api/interfaces'
const COLOR_NAMES = [
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
@@ -362,21 +361,21 @@ export class XTermFrontend extends Frontend {
}
private configureColors (scheme: TerminalColorScheme|undefined): void {
const appColorScheme = this.themes._getActiveColorScheme() as TerminalColorScheme
const config = this.configService.store
scheme = scheme ?? appColorScheme
scheme = scheme ?? this.themes._getActiveColorScheme()
const theme: ITheme = {
foreground: scheme.foreground,
selectionBackground: scheme.selection ?? '#88888888',
selectionForeground: scheme.selectionForeground ?? undefined,
background: getTerminalBackgroundColor(this.configService, this.themes, scheme) ?? '#00000000',
cursor: scheme.cursor,
cursorAccent: scheme.cursorAccent,
foreground: scheme!.foreground,
selectionBackground: scheme!.selection ?? '#88888888',
selectionForeground: scheme!.selectionForeground ?? undefined,
background: !this.themes.findCurrentTheme().followsColorScheme && config.terminal.background === 'colorScheme' ? scheme!.background : '#00000000',
cursor: scheme!.cursor,
cursorAccent: scheme!.cursorAccent,
}
for (let i = 0; i < COLOR_NAMES.length; i++) {
theme[COLOR_NAMES[i]] = scheme.colors[i]
theme[COLOR_NAMES[i]] = scheme!.colors[i]
}
if (!deepEqual(this.configuredTheme, theme)) {

View File

@@ -1,21 +0,0 @@
import { TerminalColorScheme } from './api/interfaces'
import { ConfigService, ThemesService } from 'tabby-core'
export function getTerminalBackgroundColor (
config: ConfigService,
themes: ThemesService,
scheme?: TerminalColorScheme,
): string|null {
const appTheme = themes.findCurrentTheme()
const appColorScheme = themes._getActiveColorScheme() as TerminalColorScheme
// Use non transparent background when:
// - legacy theme and user choses colorScheme based BG
// - or new theme but profile-specific scheme is used
const shouldUseCSBackground =
!appTheme.followsColorScheme && config.store.terminal.background === 'colorScheme'
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|| appTheme.followsColorScheme && scheme?.name !== appColorScheme.name
return shouldUseCSBackground && scheme ? scheme.background : null
}

View File

@@ -2,7 +2,7 @@ import '@vaadin/vaadin-context-menu'
import copyToClipboard from 'copy-text-to-clipboard'
import { Injectable, Inject } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { PlatformService, ClipboardContent, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileUploadOptions, FileDownload, HTMLFileUpload, DirectoryUpload } from 'tabby-core'
import { PlatformService, ClipboardContent, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileUploadOptions, FileDownload, HTMLFileUpload } from 'tabby-core'
// eslint-disable-next-line no-duplicate-imports
import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu'
@@ -135,10 +135,6 @@ export class WebPlatformService extends PlatformService {
})
}
async startUploadDirectory (_paths?: string[]): Promise<DirectoryUpload> {
return new DirectoryUpload()
}
setErrorHandler (handler: (_: any) => void): void {
window.addEventListener('error', handler)
}
@@ -149,7 +145,7 @@ export class WebPlatformService extends PlatformService {
}
class HTMLFileDownload extends FileDownload {
private buffers: Uint8Array[] = []
private buffers: Buffer[] = []
constructor (
private name: string,
@@ -171,8 +167,8 @@ class HTMLFileDownload extends FileDownload {
return this.size
}
async write (buffer: Uint8Array): Promise<void> {
this.buffers.push(Uint8Array.from(buffer))
async write (buffer: Buffer): Promise<void> {
this.buffers.push(Buffer.from(buffer))
this.increaseProgress(buffer.length)
if (this.isComplete()) {
this.finish()

View File

@@ -1,183 +1,183 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@polymer/iron-flex-layout@^3.0.0-pre.26":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz#36f9e1a8eb792d279b2bc75d362628721ad37f0c"
integrity sha512-7gB869czArF+HZcPTVSgvA7tXYFze9EKckvM95NB7SqYF+NnsQyhoXgKnpFwGyo95lUjUW9TFDLUwDXnCYFtkw==
dependencies:
"@polymer/polymer" "^3.0.0"
"@polymer/iron-icon@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-icon/-/iron-icon-3.0.1.tgz#93211c39d8825fe4965a68419566036c1df291eb"
integrity sha512-QLPwirk+UPZNaLnMew9VludXA4CWUCenRewgEcGYwdzVgDPCDbXxy6vRJjmweZobMQv/oVLppT2JZtJFnPxX6g==
dependencies:
"@polymer/iron-flex-layout" "^3.0.0-pre.26"
"@polymer/iron-meta" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
"@polymer/iron-iconset-svg@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-iconset-svg/-/iron-iconset-svg-3.0.1.tgz#568d6e7dbc120299dae63be3600aeba0d30ddbea"
integrity sha512-XNwURbNHRw6u2fJe05O5fMYye6GSgDlDqCO+q6K1zAnKIrpgZwf2vTkBd5uCcZwsN0FyCB3mvNZx4jkh85dRDw==
dependencies:
"@polymer/iron-meta" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
"@polymer/iron-media-query@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-media-query/-/iron-media-query-3.0.1.tgz#5cd8a1c1e8c9b8bafd3dd5da14e0f8d2cfa76d83"
integrity sha512-czUX1pm1zfmfcZtq5J57XFkcobBv08Y50exp0/3v8Bos5VL/jv2tU0RwiTfDBxUMhjicGbgwEBFQPY2V5DMzyw==
dependencies:
"@polymer/polymer" "^3.0.0"
"@polymer/iron-meta@^3.0.0-pre.26":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-meta/-/iron-meta-3.0.1.tgz#7f140628d127b0a284f882f1bb323a261bc125f5"
integrity sha512-pWguPugiLYmWFV9UWxLWzZ6gm4wBwQdDy4VULKwdHCqR7OP7u98h+XDdGZsSlDPv6qoryV/e3tGHlTIT0mbzJA==
dependencies:
"@polymer/polymer" "^3.0.0"
"@polymer/polymer@^3.0.0":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.4.1.tgz#333bef25711f8411bb5624fb3eba8212ef8bee96"
integrity sha512-KPWnhDZibtqKrUz7enIPOiO4ZQoJNOuLwqrhV2MXzIt3VVnUVJVG5ORz4Z2sgO+UZ+/UZnPD0jqY+jmw/+a9mQ==
dependencies:
"@webcomponents/shadycss" "^1.9.1"
"@vaadin/vaadin-context-menu@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-context-menu/-/vaadin-context-menu-5.0.0.tgz#c8ef7a78f107c9824ef90c9331159d5f2818fdac"
integrity sha512-+OIFseHPRy1QraQFLUT/jxCKlvsOVg/NaaHhfonTZdwrO31CTpKGZFCDB0Gvos2W9WdXa6WI12DRJLZF7Wcr0g==
dependencies:
"@polymer/iron-media-query" "^3.0.0"
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.1"
"@vaadin/vaadin-item" "^3.0.0"
"@vaadin/vaadin-list-box" "^2.0.0"
"@vaadin/vaadin-lumo-styles" "^1.6.1"
"@vaadin/vaadin-material-styles" "^1.3.2"
"@vaadin/vaadin-overlay" "^3.5.0"
"@vaadin/vaadin-themable-mixin" "^1.6.2"
"@vaadin/vaadin-development-mode-detector@^2.0.0":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-development-mode-detector/-/vaadin-development-mode-detector-2.0.4.tgz#f49c8009856bead92d248377c36b295b5aae78e5"
integrity sha512-S+PaFrZpK8uBIOnIHxjntTrgumd5ztuCnZww96ydGKXgo9whXfZsbMwDuD/102a/IuPUMyF+dh/n3PbWzJ6igA==
"@vaadin/vaadin-element-mixin@^2.4.0", "@vaadin/vaadin-element-mixin@^2.4.1":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-element-mixin/-/vaadin-element-mixin-2.4.2.tgz#3c8040a8e756bc274b7777723b1fba2b9895cd41"
integrity sha512-VSDVK0XUsFe/RohpwSzQwgqb2Pwpok6sDNhIDS4CARr3HPhq2voMzT/FowFbkEy0J1hFtN/ZfC7tkv3kdEKKIQ==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-development-mode-detector" "^2.0.0"
"@vaadin/vaadin-usage-statistics" "^2.1.0"
"@vaadin/vaadin-item@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-item/-/vaadin-item-3.0.0.tgz#abbeadd752dd46351217b94351c05bf93d6fad1c"
integrity sha512-AcSqaOd2LJr51JWT3j7GcdbU54oBHAE8xlfeN0O5OdCcsAQJLekkNJ3uxt8Kr3ZP99nnEFTZ1WKcQtEufSAVhA==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.1"
"@vaadin/vaadin-lumo-styles" "^1.6.1"
"@vaadin/vaadin-material-styles" "^1.3.2"
"@vaadin/vaadin-themable-mixin" "^1.6.2"
"@vaadin/vaadin-list-box@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-list-box/-/vaadin-list-box-2.0.0.tgz#783e1abf1dd50609a7a00a6de2acd2394a1d808e"
integrity sha512-3WU7oU3cgrp7jPet1aAjAIJSQqdVbKAqIPxOH3LsLX7QQAYnWvUwQY+UApPHiJIjpnKF0PfYiIZe1o6adqKivg==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.1"
"@vaadin/vaadin-item" "^3.0.0"
"@vaadin/vaadin-list-mixin" "^2.5.0"
"@vaadin/vaadin-lumo-styles" "^1.6.1"
"@vaadin/vaadin-material-styles" "^1.3.2"
"@vaadin/vaadin-themable-mixin" "^1.6.1"
"@vaadin/vaadin-list-mixin@^2.5.0":
version "2.5.1"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-list-mixin/-/vaadin-list-mixin-2.5.1.tgz#f6ab60cc658900d3eb7bfff18cf42d769374b659"
integrity sha512-XcMzQ0hJnK/AAiV+bW95nwJgmMIrXUBiSDwM+uvfurcBKqPyM4pm3sj8imh8zXSTfpN4HSjMnrLWU1ZfR330vg==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.1"
"@vaadin/vaadin-lumo-styles@^1.3.0", "@vaadin/vaadin-lumo-styles@^1.6.1":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-1.6.1.tgz#2099227b0f646ead16f7289e704b6a793594bf5c"
integrity sha512-Yh9ZcekpY7byXP1QJnfx94rVvK71xHBEspsVV7LL7YMvqXU4EAYuzQGYsljryV4PGS9PFPD6sqbGqhEkIhHPnQ==
dependencies:
"@polymer/iron-icon" "^3.0.0"
"@polymer/iron-iconset-svg" "^3.0.0"
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-material-styles@^1.2.0", "@vaadin/vaadin-material-styles@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-material-styles/-/vaadin-material-styles-1.3.2.tgz#d2c1bd290db16721152ae672dbe052c381686696"
integrity sha512-EFrvGScoxhLNrPnWtT2Ia77whjF2TD4jrcyeh1jv9joCA2n5SUba+4XJciVSGmopqqQato6lwRnZSvMLJX7cyw==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-overlay@^3.5.0":
version "3.5.1"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-overlay/-/vaadin-overlay-3.5.1.tgz#c4391b3c6c1f7a512b0a6f0dd96f11480feed402"
integrity sha512-0g+poK/BXF92L2lSKrHMY5rcKzUxCBZNzP/NDwgi4a86nbjL7CAKKZdno7Yl+j8UsTR76nOEw4fAYTFi86B0qg==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.0"
"@vaadin/vaadin-lumo-styles" "^1.3.0"
"@vaadin/vaadin-material-styles" "^1.2.0"
"@vaadin/vaadin-themable-mixin" "^1.6.1"
"@vaadin/vaadin-themable-mixin@^1.6.1", "@vaadin/vaadin-themable-mixin@^1.6.2":
version "1.6.2"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-1.6.2.tgz#8d619722819ba850af777579a550ff8b1d2b960f"
integrity sha512-PZZOZnke3KUlZsDrRVbWxAGEeFBPRyRayNRCvip0XnQK+Zs3cLuRgdgbdro3Ir9LZ3Izsw6HqA6XNMKffEP67A==
dependencies:
"@polymer/polymer" "^3.0.0"
lit-element "^2.0.0"
"@vaadin/vaadin-usage-statistics@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-usage-statistics/-/vaadin-usage-statistics-2.1.0.tgz#9c0fd71dded80f401bcdfbcb3f45b5640fc4256d"
integrity sha512-e81nbqY5zsaYhLJuOVkJkB/Um1pGK5POIqIlTNhUfjeoyGaJ63tiX8+D5n6F+GgVxUTLUarsKa6SKRcQel0AzA==
dependencies:
"@vaadin/vaadin-development-mode-detector" "^2.0.0"
"@webcomponents/shadycss@^1.9.1":
version "1.10.2"
resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.10.2.tgz#40e03cab6dc5e12f199949ba2b79e02f183d1e7b"
integrity sha512-9Iseu8bRtecb0klvv+WXZOVZatsRkbaH7M97Z+f+Pt909R4lDfgUODAnra23DOZTpeMTAkVpf4m/FZztN7Ox1A==
bootstrap@^4.1.3:
version "4.6.0"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.0.tgz#97b9f29ac98f98dfa43bf7468262d84392552fd7"
integrity sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==
bowser@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f"
integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==
copy-text-to-clipboard@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c"
integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==
lit-element@^2.0.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.5.1.tgz#3fa74b121a6cd22902409ae3859b7847d01aa6b6"
integrity sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==
dependencies:
lit-html "^1.1.1"
lit-html@^1.1.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.4.1.tgz#0c6f3ee4ad4eb610a49831787f0478ad8e9ae5e0"
integrity sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@polymer/iron-flex-layout@^3.0.0-pre.26":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz#36f9e1a8eb792d279b2bc75d362628721ad37f0c"
integrity sha512-7gB869czArF+HZcPTVSgvA7tXYFze9EKckvM95NB7SqYF+NnsQyhoXgKnpFwGyo95lUjUW9TFDLUwDXnCYFtkw==
dependencies:
"@polymer/polymer" "^3.0.0"
"@polymer/iron-icon@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-icon/-/iron-icon-3.0.1.tgz#93211c39d8825fe4965a68419566036c1df291eb"
integrity sha512-QLPwirk+UPZNaLnMew9VludXA4CWUCenRewgEcGYwdzVgDPCDbXxy6vRJjmweZobMQv/oVLppT2JZtJFnPxX6g==
dependencies:
"@polymer/iron-flex-layout" "^3.0.0-pre.26"
"@polymer/iron-meta" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
"@polymer/iron-iconset-svg@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-iconset-svg/-/iron-iconset-svg-3.0.1.tgz#568d6e7dbc120299dae63be3600aeba0d30ddbea"
integrity sha512-XNwURbNHRw6u2fJe05O5fMYye6GSgDlDqCO+q6K1zAnKIrpgZwf2vTkBd5uCcZwsN0FyCB3mvNZx4jkh85dRDw==
dependencies:
"@polymer/iron-meta" "^3.0.0-pre.26"
"@polymer/polymer" "^3.0.0"
"@polymer/iron-media-query@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-media-query/-/iron-media-query-3.0.1.tgz#5cd8a1c1e8c9b8bafd3dd5da14e0f8d2cfa76d83"
integrity sha512-czUX1pm1zfmfcZtq5J57XFkcobBv08Y50exp0/3v8Bos5VL/jv2tU0RwiTfDBxUMhjicGbgwEBFQPY2V5DMzyw==
dependencies:
"@polymer/polymer" "^3.0.0"
"@polymer/iron-meta@^3.0.0-pre.26":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@polymer/iron-meta/-/iron-meta-3.0.1.tgz#7f140628d127b0a284f882f1bb323a261bc125f5"
integrity sha512-pWguPugiLYmWFV9UWxLWzZ6gm4wBwQdDy4VULKwdHCqR7OP7u98h+XDdGZsSlDPv6qoryV/e3tGHlTIT0mbzJA==
dependencies:
"@polymer/polymer" "^3.0.0"
"@polymer/polymer@^3.0.0":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.4.1.tgz#333bef25711f8411bb5624fb3eba8212ef8bee96"
integrity sha512-KPWnhDZibtqKrUz7enIPOiO4ZQoJNOuLwqrhV2MXzIt3VVnUVJVG5ORz4Z2sgO+UZ+/UZnPD0jqY+jmw/+a9mQ==
dependencies:
"@webcomponents/shadycss" "^1.9.1"
"@vaadin/vaadin-context-menu@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-context-menu/-/vaadin-context-menu-5.0.0.tgz#c8ef7a78f107c9824ef90c9331159d5f2818fdac"
integrity sha512-+OIFseHPRy1QraQFLUT/jxCKlvsOVg/NaaHhfonTZdwrO31CTpKGZFCDB0Gvos2W9WdXa6WI12DRJLZF7Wcr0g==
dependencies:
"@polymer/iron-media-query" "^3.0.0"
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.1"
"@vaadin/vaadin-item" "^3.0.0"
"@vaadin/vaadin-list-box" "^2.0.0"
"@vaadin/vaadin-lumo-styles" "^1.6.1"
"@vaadin/vaadin-material-styles" "^1.3.2"
"@vaadin/vaadin-overlay" "^3.5.0"
"@vaadin/vaadin-themable-mixin" "^1.6.2"
"@vaadin/vaadin-development-mode-detector@^2.0.0":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-development-mode-detector/-/vaadin-development-mode-detector-2.0.4.tgz#f49c8009856bead92d248377c36b295b5aae78e5"
integrity sha512-S+PaFrZpK8uBIOnIHxjntTrgumd5ztuCnZww96ydGKXgo9whXfZsbMwDuD/102a/IuPUMyF+dh/n3PbWzJ6igA==
"@vaadin/vaadin-element-mixin@^2.4.0", "@vaadin/vaadin-element-mixin@^2.4.1":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-element-mixin/-/vaadin-element-mixin-2.4.2.tgz#3c8040a8e756bc274b7777723b1fba2b9895cd41"
integrity sha512-VSDVK0XUsFe/RohpwSzQwgqb2Pwpok6sDNhIDS4CARr3HPhq2voMzT/FowFbkEy0J1hFtN/ZfC7tkv3kdEKKIQ==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-development-mode-detector" "^2.0.0"
"@vaadin/vaadin-usage-statistics" "^2.1.0"
"@vaadin/vaadin-item@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-item/-/vaadin-item-3.0.0.tgz#abbeadd752dd46351217b94351c05bf93d6fad1c"
integrity sha512-AcSqaOd2LJr51JWT3j7GcdbU54oBHAE8xlfeN0O5OdCcsAQJLekkNJ3uxt8Kr3ZP99nnEFTZ1WKcQtEufSAVhA==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.1"
"@vaadin/vaadin-lumo-styles" "^1.6.1"
"@vaadin/vaadin-material-styles" "^1.3.2"
"@vaadin/vaadin-themable-mixin" "^1.6.2"
"@vaadin/vaadin-list-box@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-list-box/-/vaadin-list-box-2.0.0.tgz#783e1abf1dd50609a7a00a6de2acd2394a1d808e"
integrity sha512-3WU7oU3cgrp7jPet1aAjAIJSQqdVbKAqIPxOH3LsLX7QQAYnWvUwQY+UApPHiJIjpnKF0PfYiIZe1o6adqKivg==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.1"
"@vaadin/vaadin-item" "^3.0.0"
"@vaadin/vaadin-list-mixin" "^2.5.0"
"@vaadin/vaadin-lumo-styles" "^1.6.1"
"@vaadin/vaadin-material-styles" "^1.3.2"
"@vaadin/vaadin-themable-mixin" "^1.6.1"
"@vaadin/vaadin-list-mixin@^2.5.0":
version "2.5.1"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-list-mixin/-/vaadin-list-mixin-2.5.1.tgz#f6ab60cc658900d3eb7bfff18cf42d769374b659"
integrity sha512-XcMzQ0hJnK/AAiV+bW95nwJgmMIrXUBiSDwM+uvfurcBKqPyM4pm3sj8imh8zXSTfpN4HSjMnrLWU1ZfR330vg==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.1"
"@vaadin/vaadin-lumo-styles@^1.3.0", "@vaadin/vaadin-lumo-styles@^1.6.1":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-1.6.1.tgz#2099227b0f646ead16f7289e704b6a793594bf5c"
integrity sha512-Yh9ZcekpY7byXP1QJnfx94rVvK71xHBEspsVV7LL7YMvqXU4EAYuzQGYsljryV4PGS9PFPD6sqbGqhEkIhHPnQ==
dependencies:
"@polymer/iron-icon" "^3.0.0"
"@polymer/iron-iconset-svg" "^3.0.0"
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-material-styles@^1.2.0", "@vaadin/vaadin-material-styles@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-material-styles/-/vaadin-material-styles-1.3.2.tgz#d2c1bd290db16721152ae672dbe052c381686696"
integrity sha512-EFrvGScoxhLNrPnWtT2Ia77whjF2TD4jrcyeh1jv9joCA2n5SUba+4XJciVSGmopqqQato6lwRnZSvMLJX7cyw==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-overlay@^3.5.0":
version "3.5.1"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-overlay/-/vaadin-overlay-3.5.1.tgz#c4391b3c6c1f7a512b0a6f0dd96f11480feed402"
integrity sha512-0g+poK/BXF92L2lSKrHMY5rcKzUxCBZNzP/NDwgi4a86nbjL7CAKKZdno7Yl+j8UsTR76nOEw4fAYTFi86B0qg==
dependencies:
"@polymer/polymer" "^3.0.0"
"@vaadin/vaadin-element-mixin" "^2.4.0"
"@vaadin/vaadin-lumo-styles" "^1.3.0"
"@vaadin/vaadin-material-styles" "^1.2.0"
"@vaadin/vaadin-themable-mixin" "^1.6.1"
"@vaadin/vaadin-themable-mixin@^1.6.1", "@vaadin/vaadin-themable-mixin@^1.6.2":
version "1.6.2"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-1.6.2.tgz#8d619722819ba850af777579a550ff8b1d2b960f"
integrity sha512-PZZOZnke3KUlZsDrRVbWxAGEeFBPRyRayNRCvip0XnQK+Zs3cLuRgdgbdro3Ir9LZ3Izsw6HqA6XNMKffEP67A==
dependencies:
"@polymer/polymer" "^3.0.0"
lit-element "^2.0.0"
"@vaadin/vaadin-usage-statistics@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@vaadin/vaadin-usage-statistics/-/vaadin-usage-statistics-2.1.0.tgz#9c0fd71dded80f401bcdfbcb3f45b5640fc4256d"
integrity sha512-e81nbqY5zsaYhLJuOVkJkB/Um1pGK5POIqIlTNhUfjeoyGaJ63tiX8+D5n6F+GgVxUTLUarsKa6SKRcQel0AzA==
dependencies:
"@vaadin/vaadin-development-mode-detector" "^2.0.0"
"@webcomponents/shadycss@^1.9.1":
version "1.10.2"
resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.10.2.tgz#40e03cab6dc5e12f199949ba2b79e02f183d1e7b"
integrity sha512-9Iseu8bRtecb0klvv+WXZOVZatsRkbaH7M97Z+f+Pt909R4lDfgUODAnra23DOZTpeMTAkVpf4m/FZztN7Ox1A==
bootstrap@^4.1.3:
version "4.6.0"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.0.tgz#97b9f29ac98f98dfa43bf7468262d84392552fd7"
integrity sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==
bowser@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f"
integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==
copy-text-to-clipboard@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c"
integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==
lit-element@^2.0.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.5.1.tgz#3fa74b121a6cd22902409ae3859b7847d01aa6b6"
integrity sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==
dependencies:
lit-html "^1.1.1"
lit-html@^1.1.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.4.1.tgz#0c6f3ee4ad4eb610a49831787f0478ad8e9ae5e0"
integrity sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==

File diff suppressed because it is too large Load Diff

View File

@@ -157,7 +157,6 @@ export default options => {
'os',
'path',
'readline',
'russh',
'@luminati-io/socksv5',
'stream',
'windows-native-registry',

View File

@@ -1553,6 +1553,13 @@ asn1.js@^5.2.0:
minimalistic-assert "^1.0.0"
safer-buffer "^2.1.0"
asn1@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
dependencies:
safer-buffer "~2.1.0"
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
@@ -1687,7 +1694,7 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bcrypt-pbkdf@^1.0.0:
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
@@ -1907,6 +1914,11 @@ buffer@^5.1.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buildcheck@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==
builder-util-runtime@9.2.1:
version "9.2.1"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz#3184dcdf7ed6c47afb8df733813224ced4f624fd"
@@ -2495,6 +2507,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cpu-features@~0.0.9:
version "0.0.10"
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5"
integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==
dependencies:
buildcheck "~0.0.6"
nan "^2.19.0"
crc@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
@@ -5603,10 +5623,10 @@ lzma-native@^8.0.5, lzma-native@^8.0.6:
node-gyp-build "^4.2.1"
readable-stream "^3.6.0"
macos-release@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.3.0.tgz#92cb67bc66d67c3fde4a9e14f5f909afa418b072"
integrity sha512-tPJQ1HeyiU2vRruNGhZ+VleWuMQRro8iFtJxYgnS4NQe+EukKF6aGiIT+7flZhISAt2iaXBCfFGvAyif7/f8nQ==
macos-release@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.1.0.tgz#6165bb0736ae567ed6649e36ce6a24d87cbb7aca"
integrity sha512-/M/R0gCDgM+Cv1IuBG1XGdfTFnMEG6PZeT+KGWHO/OG+imqmaD9CH5vHBTycEM3+Kc4uG2Il+tFAuUWLqQOeUA==
magic-string@^0.27.0:
version "0.27.0"
@@ -5965,7 +5985,7 @@ mute-stream@~0.0.4:
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
nan@2.17.0:
nan@2.17.0, nan@^2.18.0, nan@^2.19.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
@@ -8200,6 +8220,17 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
ssh2@^1.14.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.15.0.tgz#2f998455036a7f89e0df5847efb5421748d9871b"
integrity sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==
dependencies:
asn1 "^0.2.6"
bcrypt-pbkdf "^1.0.2"
optionalDependencies:
cpu-features "~0.0.9"
nan "^2.18.0"
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"