mirror of
https://github.com/coder/code-server.git
synced 2026-04-14 06:24:32 -05:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94ef3776ad | ||
|
|
a82f1ceaa5 | ||
|
|
25254214fb | ||
|
|
feb5e6770c | ||
|
|
e37b35278d | ||
|
|
2ffbcfdfcb | ||
|
|
74da5167a2 | ||
|
|
daac46b3cf | ||
|
|
ea3c79ffd1 | ||
|
|
93e60f7b0e | ||
|
|
7926647058 | ||
|
|
d27cd43dce | ||
|
|
1befd37310 | ||
|
|
2b3b5e7051 | ||
|
|
788da5d495 | ||
|
|
070a5a3179 | ||
|
|
7cbb6a8b09 | ||
|
|
7fb54073db | ||
|
|
e72e8bb6b5 | ||
|
|
411846528b | ||
|
|
7f024eda6e | ||
|
|
2617623ed6 | ||
|
|
70aa1b7722 | ||
|
|
90a4147381 | ||
|
|
5c19962930 | ||
|
|
56d10d82bf | ||
|
|
cef2aa22dc | ||
|
|
b5a9ef80e7 | ||
|
|
5d3c9edce4 | ||
|
|
9955cd91a4 | ||
|
|
fdeaba9581 | ||
|
|
40ff2e6049 | ||
|
|
73d42f7ea0 | ||
|
|
fb73742b2b | ||
|
|
ccb0d3a34f | ||
|
|
4a121edd16 | ||
|
|
f6db985712 | ||
|
|
6d00fc7f46 | ||
|
|
ee024f3f2d | ||
|
|
0703ef008c | ||
|
|
2109d1cf6a | ||
|
|
74af05dfbe | ||
|
|
2798322b03 | ||
|
|
b3b971480f | ||
|
|
6745a46034 | ||
|
|
43ef50b404 | ||
|
|
45c89856fd | ||
|
|
5f4ae75431 | ||
|
|
521ff44aed | ||
|
|
5708e6ce32 | ||
|
|
3f7db15fde | ||
|
|
8c99f41b90 | ||
|
|
e02d56dbfd | ||
|
|
aac5efa046 | ||
|
|
ff2764f7b1 | ||
|
|
bda6b631ab | ||
|
|
9bac70ea0d | ||
|
|
7722ef1437 | ||
|
|
951d8ac45e | ||
|
|
2e17735795 | ||
|
|
1da7cda39e | ||
|
|
c829d74203 | ||
|
|
39075b2cf3 | ||
|
|
aed1fc0119 | ||
|
|
6e411adb01 | ||
|
|
36ba646bcb | ||
|
|
278d0f2184 | ||
|
|
b954250018 | ||
|
|
2b72da22e6 | ||
|
|
3c838e2d02 | ||
|
|
f8d563c49b | ||
|
|
39e63af359 | ||
|
|
c995988b70 | ||
|
|
a44bd71043 | ||
|
|
19bcd043d7 | ||
|
|
c32a31d802 | ||
|
|
45ca68c362 | ||
|
|
8f1a70339d | ||
|
|
35209aa5b5 | ||
|
|
87f44599e5 | ||
|
|
71f01ec0f0 | ||
|
|
f8e2d00099 | ||
|
|
a485fec27c | ||
|
|
aa2276293a | ||
|
|
72eae01684 | ||
|
|
bca733de3d | ||
|
|
78282a1fd6 | ||
|
|
dbdd2edb62 | ||
|
|
a9d61daa91 | ||
|
|
6c08466b05 | ||
|
|
85e083580d | ||
|
|
b0431069a1 | ||
|
|
f9cc07926b | ||
|
|
be40eca5d9 | ||
|
|
9ba08907da |
12
.github/workflows/build.yaml
vendored
12
.github/workflows/build.yaml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v26.1
|
||||
uses: tj-actions/changed-files@v37
|
||||
with:
|
||||
files: |
|
||||
docs/**
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v26.1
|
||||
uses: tj-actions/changed-files@v37
|
||||
with:
|
||||
files: |
|
||||
ci/helm-chart/**
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v26.1
|
||||
uses: tj-actions/changed-files@v37
|
||||
with:
|
||||
files: |
|
||||
**/*.ts
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v26.1
|
||||
uses: tj-actions/changed-files@v37
|
||||
with:
|
||||
files: |
|
||||
**/*.ts
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
build:
|
||||
name: Build code-server
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
steps:
|
||||
@@ -248,7 +248,7 @@ jobs:
|
||||
# different and we need to rebuild.
|
||||
- name: Get latest lib/vscode rev
|
||||
id: vscode-rev
|
||||
run: echo "::set-output name=rev::$(git rev-parse HEAD:./lib/vscode)"
|
||||
run: echo "rev=$(git rev-parse HEAD:./lib/vscode)" >> $GITHUB_OUTPUT
|
||||
|
||||
# We need to rebuild when we have a new version of Code, when any of
|
||||
# the patches changed, or when the code-server version changes (since
|
||||
|
||||
14
.github/workflows/publish.yaml
vendored
14
.github/workflows/publish.yaml
vendored
@@ -29,8 +29,14 @@ jobs:
|
||||
- name: Checkout code-server
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
cache: "yarn"
|
||||
|
||||
- name: Download npm package from release artifacts
|
||||
uses: robinraju/release-downloader@v1.7
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
with:
|
||||
repository: "coder/code-server"
|
||||
tag: ${{ github.event.inputs.version || github.ref_name }}
|
||||
@@ -141,7 +147,7 @@ jobs:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
run: |
|
||||
git checkout -b update-version-${{ env.VERSION }}
|
||||
git add .
|
||||
git add .
|
||||
git commit -m "chore: updating version to ${{ env.VERSION }}"
|
||||
git push -u origin $(git branch --show)
|
||||
gh pr create --repo coder/code-server-aur --title "chore: bump version to ${{ env.VERSION }}" --body "PR opened by @$GITHUB_ACTOR" --assignee $GITHUB_ACTOR
|
||||
@@ -178,7 +184,7 @@ jobs:
|
||||
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download release artifacts
|
||||
uses: robinraju/release-downloader@v1.7
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
with:
|
||||
repository: "coder/code-server"
|
||||
tag: v${{ env.VERSION }}
|
||||
@@ -186,7 +192,7 @@ jobs:
|
||||
out-file-path: "release-packages"
|
||||
|
||||
- name: Publish to Docker
|
||||
run: yarn publish:docker
|
||||
run: ./ci/steps/docker-buildx-push.sh
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
7
.github/workflows/security.yaml
vendored
7
.github/workflows/security.yaml
vendored
@@ -12,9 +12,8 @@ on:
|
||||
# Runs every Monday morning PST
|
||||
- cron: "17 15 * * 1"
|
||||
|
||||
# Cancel in-progress runs for pull requests when developers push
|
||||
# additional changes, and serialize builds in branches.
|
||||
# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-concurrency-to-cancel-any-in-progress-job-or-run
|
||||
# Cancel in-progress runs for pull requests when developers push additional
|
||||
# changes, and serialize builds in branches.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
@@ -65,7 +64,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Trivy vulnerability scanner in repo mode
|
||||
uses: aquasecurity/trivy-action@cff3e9a7f62c41dd51975266d0ae235709e39c41
|
||||
uses: aquasecurity/trivy-action@41f05d9ecffa2ed3f1580af306000f734b733e54
|
||||
with:
|
||||
scan-type: "fs"
|
||||
scan-ref: "."
|
||||
|
||||
2
.github/workflows/trivy-docker.yaml
vendored
2
.github/workflows/trivy-docker.yaml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Trivy vulnerability scanner in image mode
|
||||
uses: aquasecurity/trivy-action@cff3e9a7f62c41dd51975266d0ae235709e39c41
|
||||
uses: aquasecurity/trivy-action@41f05d9ecffa2ed3f1580af306000f734b733e54
|
||||
with:
|
||||
image-ref: "docker.io/codercom/code-server:latest"
|
||||
ignore-unfixed: true
|
||||
|
||||
110
CHANGELOG.md
110
CHANGELOG.md
@@ -22,15 +22,115 @@ Code v99.99.999
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [4.16.0](https://github.com/coder/code-server/releases/tag/v4.16.0) - 2023-07-28
|
||||
|
||||
Code v1.80.1
|
||||
|
||||
### Added
|
||||
|
||||
- `--disable-proxy` flag. This disables the domain and path proxies but it does
|
||||
not disable the ports panel in Code. That can be disabled by using
|
||||
`remote.autoForwardPorts=false` in your settings.
|
||||
|
||||
## [4.15.0](https://github.com/coder/code-server/releases/tag/v4.15.0) - 2023-07-21
|
||||
|
||||
Code v1.80.1
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.80.1.
|
||||
|
||||
### Added
|
||||
|
||||
- `--trusted-origin` flag for specifying origins that you trust but do not
|
||||
control (for example a reverse proxy).
|
||||
|
||||
Code v1.79.2
|
||||
|
||||
## [4.14.1](https://github.com/coder/code-server/releases/tag/v4.14.1) - 2023-06-26
|
||||
|
||||
Code v1.79.2
|
||||
|
||||
### Security
|
||||
|
||||
- Remove extra write permissions on the Node binary bundled with the linux-amd64
|
||||
tarball. If you extract the tar without a umask this could mean the Node
|
||||
binary would be unexpectedly writable.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Inability to launch multiple instances of code-server for different users.
|
||||
|
||||
### Added
|
||||
|
||||
- `--session-socket` CLI flag to configure the location of the session socket.
|
||||
By default it will be placed in `<user data dir>/code-server-ipc.sock`.
|
||||
|
||||
## [4.14.0](https://github.com/coder/code-server/releases/tag/v4.14.0) - 2023-06-16
|
||||
|
||||
Code v1.79.2
|
||||
|
||||
### Added
|
||||
|
||||
- `--domain-proxy` now supports `{{port}}` and `{{host}}` template variables.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.79.2
|
||||
- Files opened from an external terminal will now open in the most closely
|
||||
related window rather than in the last opened window.
|
||||
|
||||
## [4.13.0](https://github.com/coder/code-server/releases/tag/v4.13.0) - 2023-05-19
|
||||
|
||||
Code v1.78.2
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.78.2.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Proxying files that contain non-ASCII characters.
|
||||
- Origin check when X-Forwarded-Host contains comma-separated hosts.
|
||||
|
||||
## [4.12.0](https://github.com/coder/code-server/releases/tag/v4.12.0) - 2023-04-21
|
||||
|
||||
Code v1.77.3
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.77.3
|
||||
- Ports panel will use domain-based proxy (instead of the default path-based
|
||||
proxy) when set via --proxy-domain.
|
||||
- Apply --app-name to the PWA title.
|
||||
|
||||
### Added
|
||||
|
||||
- Thai translation for login page.
|
||||
- Debug logs around the origin security check. If you are getting forbidden
|
||||
errors on web sockets please run code-server with `--log debug` to see why the
|
||||
requests are being blocked.
|
||||
|
||||
## [4.11.0](https://github.com/coder/code-server/releases/tag/v4.11.0) - 2023-03-16
|
||||
|
||||
Code v1.76.1
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Code 1.76.1
|
||||
|
||||
## [4.10.1](https://github.com/coder/code-server/releases/tag/v4.10.1) - 2023-03-04
|
||||
|
||||
Code v1.75.1
|
||||
|
||||
### Security
|
||||
|
||||
Add an origin check to web sockets to prevent a cross-site hijacking attack that
|
||||
affects those who use older or niche browsers that do not support SameSite
|
||||
cookies and those who access code-server under a shared domain with other users
|
||||
on separate sub-domains. The check requires the host header to be set so if you
|
||||
use a reverse proxy ensure it forwards that information.
|
||||
Added an origin check to web sockets to prevent cross-site hijacking attacks on
|
||||
users using older or niche browser that do not support SameSite cookies and
|
||||
attacks across sub-domains that share the same root domain.
|
||||
|
||||
The check requires the host header to be set so if you use a reverse proxy
|
||||
ensure it forwards that information otherwise web sockets will be blocked.
|
||||
|
||||
## [4.10.0](https://github.com/coder/code-server/releases/tag/v4.10.0) - 2023-02-15
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ This directory contains scripts used for the development of code-server.
|
||||
- [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`)
|
||||
- Starts a process to build and launch code-server and restart on any code changes.
|
||||
- Example usage in [./docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md).
|
||||
- [./ci/dev/gen_icons.sh](./ci/dev/gen_icons.sh) (`yarn icons`)
|
||||
- [./ci/dev/gen_icons.sh](./dev/gen_icons.sh) (`yarn icons`)
|
||||
- Generates the various icons from a single `.svg` favicon in
|
||||
`src/browser/media/favicon.svg`.
|
||||
- Requires [imagemagick](https://imagemagick.org/index.php)
|
||||
@@ -75,7 +75,7 @@ You can disable minification by setting `MINIFY=`.
|
||||
|
||||
This directory contains the release docker container image.
|
||||
|
||||
- [./ci/steps/build-docker-buildx-push.sh](./ci/steps/docker-buildx-push.sh)
|
||||
- [./ci/steps/build-docker-buildx-push.sh](./steps/docker-buildx-push.sh)
|
||||
- Builds the release containers with tags `codercom/code-server-$ARCH:$VERSION` for amd64 and arm64 with `docker buildx` and pushes them.
|
||||
- Assumes debian releases are ready in `./release-packages`.
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ main() {
|
||||
rsync ./ci/build/code-server.sh "$RELEASE_PATH/bin/code-server"
|
||||
rsync "$node_path" "$RELEASE_PATH/lib/node"
|
||||
|
||||
chmod 755 "$RELEASE_PATH/lib/node"
|
||||
|
||||
pushd "$RELEASE_PATH"
|
||||
npm install --unsafe-perm --omit=dev
|
||||
popd
|
||||
|
||||
@@ -15,7 +15,7 @@ copy-bin-script() {
|
||||
local dest="lib/vscode-reh-web-linux-x64/bin/$script"
|
||||
cp "lib/vscode/resources/server/bin/$script" "$dest"
|
||||
sed -i.bak "s/@@VERSION@@/$(vscode_version)/g" "$dest"
|
||||
sed -i.bak "s/@@COMMIT@@/$VSCODE_DISTRO_COMMIT/g" "$dest"
|
||||
sed -i.bak "s/@@COMMIT@@/$BUILD_SOURCEVERSION/g" "$dest"
|
||||
sed -i.bak "s/@@APPNAME@@/code-server/g" "$dest"
|
||||
|
||||
# Fix Node path on Darwin and Linux.
|
||||
@@ -51,8 +51,8 @@ main() {
|
||||
# Set the commit Code will embed into the product.json. We need to do this
|
||||
# since Code tries to get the commit from the `.git` directory which will fail
|
||||
# as it is a submodule.
|
||||
export VSCODE_DISTRO_COMMIT
|
||||
VSCODE_DISTRO_COMMIT=$(git rev-parse HEAD)
|
||||
export BUILD_SOURCEVERSION
|
||||
BUILD_SOURCEVERSION=$(git rev-parse HEAD)
|
||||
|
||||
# Add the date, our name, links, and enable telemetry (this just makes
|
||||
# telemetry available; telemetry can still be disabled by flag or setting).
|
||||
@@ -109,6 +109,15 @@ EOF
|
||||
|
||||
popd
|
||||
|
||||
pushd lib/vscode-reh-web-linux-x64
|
||||
# Make sure Code took the version we set in the environment variable. Not
|
||||
# having a version will break display languages.
|
||||
if ! jq -e .commit product.json; then
|
||||
echo "'commit' is missing from product.json"
|
||||
exit 1
|
||||
fi
|
||||
popd
|
||||
|
||||
# These provide a `code-server` command in the integrated terminal to open
|
||||
# files in the current instance.
|
||||
delete-bin-script remote-cli/code-server
|
||||
|
||||
@@ -15,9 +15,9 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 3.6.0
|
||||
version: 3.12.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
appVersion: 4.10.0
|
||||
appVersion: 4.16.0
|
||||
|
||||
@@ -6,7 +6,7 @@ replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: codercom/code-server
|
||||
tag: '4.9.1'
|
||||
tag: '4.16.0'
|
||||
pullPolicy: Always
|
||||
|
||||
# Specifies one or more secrets to be used when pulling images from a
|
||||
|
||||
37
docs/FAQ.md
37
docs/FAQ.md
@@ -14,6 +14,7 @@
|
||||
- [How do I install an extension manually?](#how-do-i-install-an-extension-manually)
|
||||
- [How do I use my own extensions marketplace?](#how-do-i-use-my-own-extensions-marketplace)
|
||||
- [Where are extensions stored?](#where-are-extensions-stored)
|
||||
- [Where is VS Code configuration stored?](#where-is-vs-code-configuration-stored)
|
||||
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
|
||||
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
|
||||
- [How do I access my Documents/Downloads/Desktop folders in code-server on macOS?](#how-do-i-access-my-documentsdownloadsdesktop-folders-in-code-server-on-macos)
|
||||
@@ -34,6 +35,7 @@
|
||||
- [Are there community projects involving code-server?](#are-there-community-projects-involving-code-server)
|
||||
- [How do I change the port?](#how-do-i-change-the-port)
|
||||
- [How do I hide the coder/coder promotion in Help: Getting Started?](#how-do-i-hide-the-codercoder-promotion-in-help-getting-started)
|
||||
- [How do I disable the proxy?](#how-do-i-disable-the-proxy)
|
||||
- [How do I disable file download?](#how-do-i-disable-file-download)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
@@ -88,6 +90,12 @@ app (PWA):
|
||||
1. Start the editor
|
||||
2. Click the **plus** icon in the URL toolbar to install the PWA
|
||||
|
||||
If you use Firefox, you can use the appropriate extension to install PWA.
|
||||
|
||||
1. Go to the installation [website](https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/) of the add-on
|
||||
2. Add the add-on to Firefox
|
||||
3. Follow the os-specific instructions on how to install the runtime counterpart
|
||||
|
||||
For other browsers, you'll have to remap keybindings for shortcuts to work.
|
||||
|
||||
## Why can't code-server use Microsoft's extension marketplace?
|
||||
@@ -185,10 +193,20 @@ docs](https://github.com/VSCodium/vscodium/blob/master/DOCS.md#extensions--marke
|
||||
|
||||
## Where are extensions stored?
|
||||
|
||||
Extensions are store, by default, to `~/.local/share/code-server/extensions`.
|
||||
Extensions are stored in `~/.local/share/code-server/extensions` by default.
|
||||
|
||||
If you set the `XDG_DATA_HOME` environment variable, the data directory will be
|
||||
`$XDG_DATA_HOME/code-server/extensions`. In general, we try to follow the XDG directory spec.
|
||||
On Linux and macOS if you set the `XDG_DATA_HOME` environment variable, the
|
||||
extensions directory will be `$XDG_DATA_HOME/code-server/extensions`. In
|
||||
general, we try to follow the XDG directory spec.
|
||||
|
||||
## Where is VS Code configuration stored?
|
||||
|
||||
VS Code configuration such as settings and keybindings are stored in
|
||||
`~/.local/share/code-server` by default.
|
||||
|
||||
On Linux and macOS if you set the `XDG_DATA_HOME` environment variable, the data
|
||||
directory will be `$XDG_DATA_HOME/code-server`. In general, we try to follow the
|
||||
XDG directory spec.
|
||||
|
||||
## How can I reuse my VS Code configuration?
|
||||
|
||||
@@ -447,6 +465,19 @@ You can pass the flag `--disable-getting-started-override` to `code-server` or
|
||||
you can set the environment variable `CS_DISABLE_GETTING_STARTED_OVERRIDE=1` or
|
||||
`CS_DISABLE_GETTING_STARTED_OVERRIDE=true`.
|
||||
|
||||
## How do I disable the proxy?
|
||||
|
||||
You can pass the flag `--disable-proxy` to `code-server` or
|
||||
you can set the environment variable `CS_DISABLE_PROXY=1` or
|
||||
`CS_DISABLE_PROXY=true`.
|
||||
|
||||
Note, this option currently only disables the proxy routes to forwarded ports, including
|
||||
the domain and path proxy routes over HTTP and WebSocket; however, it does not
|
||||
disable the automatic port forwarding in the VS Code workbench itself. In other words,
|
||||
user will still see the Ports tab and notifications, but will not be able to actually
|
||||
use access the ports. It is recommended to set `remote.autoForwardPorts` to `false`
|
||||
when using the option.
|
||||
|
||||
## How do I disable file download?
|
||||
|
||||
You can pass the flag `--disable-file-downloads` to `code-server`
|
||||
|
||||
@@ -21,3 +21,11 @@ nvm use 16
|
||||
8. Install code-server globally on device with: `npm install --global code-server --unsafe-perm`
|
||||
9. Run code-server with `code-server`
|
||||
10. Access on localhost:8080 in your browser
|
||||
|
||||
# Running code-server using Nix-on-Droid
|
||||
|
||||
1. Install Nix-on-Droid from [F-Droid](https://f-droid.org/packages/com.termux.nix/)
|
||||
2. Start app
|
||||
3. Spawn a shell with code-server by running `nix-shell -p code-server`
|
||||
4. Run code-server with `code-server`
|
||||
5. Access on localhost:8080 in your browser
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
- [Proxying to create a React app](#proxying-to-create-a-react-app)
|
||||
- [Proxying to a Vue app](#proxying-to-a-vue-app)
|
||||
- [Proxying to an Angular app](#proxying-to-an-angular-app)
|
||||
- [Proxying to a Svelte app](#proxying-to-a-svelte-app)
|
||||
- [SSH into code-server on VS Code](#ssh-into-code-server-on-vs-code)
|
||||
- [Option 1: cloudflared tunnel](#option-1-cloudflared-tunnel)
|
||||
- [Option 2: ngrok tunnel](#option-2-ngrok-tunnel)
|
||||
@@ -414,6 +415,27 @@ In order to use code-server's built-in proxy with Angular, you need to make the
|
||||
|
||||
For additional context, see [this GitHub Discussion](https://github.com/coder/code-server/discussions/5439#discussioncomment-3371983).
|
||||
|
||||
### Proxying to a Svelte app
|
||||
|
||||
In order to use code-server's built-in proxy with Svelte, you need to make the following changes in your app:
|
||||
|
||||
1. Add `svelte.config.js` if you don't already have one
|
||||
2. Update the values to match this (you can use any free port):
|
||||
|
||||
```js
|
||||
const config = {
|
||||
kit: {
|
||||
paths: {
|
||||
base: "/absproxy/5173",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
3. Access app at `<code-server-root>/absproxy/5173/` e.g. `http://localhost:8080/absproxy/5173/
|
||||
|
||||
For additional context, see [this Github Issue](https://github.com/sveltejs/kit/issues/2958)
|
||||
|
||||
## SSH into code-server on VS Code
|
||||
|
||||
[](https://ohmyz.sh/) [](https://img.shields.io/badge/Terminal-2E2E2E?style=for-the-badge&logo=Windows+Terminal&logoColor=ffffff) [](vscode:extension/ms-vscode-remote.remote-ssh)
|
||||
|
||||
@@ -97,7 +97,7 @@ code-server
|
||||
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
|
||||
```
|
||||
|
||||
A `postinstall.sh` script will attempt to run. Select your terminal (e.g., Git bash) as the default application for `.sh` files. If an additional dialog does not appear, run the install command again.
|
||||
A `postinstall.sh` script will attempt to run. Select your terminal (e.g., Git bash) as the default shell for npm run-scripts. If an additional dialog does not appear, run the install command again.
|
||||
|
||||
If the `code-server` command is not found, you'll need to [add a directory to your PATH](https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/). To find the directory, use the following command:
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
- [Upgrade](#upgrade)
|
||||
- [Known Issues](#known-issues)
|
||||
- [Git won't work in `/sdcard`](#git-wont-work-in-sdcard)
|
||||
- [Many extensions including language packs fail to install](#many-extensions-including-language-packs-fail-to-install)
|
||||
- [Extra](#extra)
|
||||
- [Create a new user](#create-a-new-user)
|
||||
- [Install Go](#install-go)
|
||||
@@ -87,6 +88,37 @@ Potential Workaround :
|
||||
1. Create a soft-link from the debian-fs to your folder in `/sdcard`
|
||||
2. Use git from termux (preferred)
|
||||
|
||||
### Many extensions including language packs fail to install
|
||||
|
||||
Issue: Android is not seen as a Linux environment but as a separate, unsupported platform, so code-server only allows [Web Extensions](https://code.visualstudio.com/api/extension-guides/web-extensions), refusing to download extensions that run on the server.\
|
||||
Fix: None\
|
||||
Potential workarounds :
|
||||
|
||||
Either
|
||||
|
||||
- Manually download extensions as `.vsix` file and install them via `Extensions: Install from VSIX...` in the Command Palette.
|
||||
|
||||
- Use an override to pretend the platform is Linux:
|
||||
|
||||
Create a JS script that patches `process.platform`:
|
||||
|
||||
```js
|
||||
// android-as-linux.js
|
||||
Object.defineProperty(process, "platform", {
|
||||
get() {
|
||||
return "linux"
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Then use Node's `--require` option to make sure it is loaded before `code-server` starts:
|
||||
|
||||
```sh
|
||||
NODE_OPTIONS="--require /path/to/android-as-linux.js" code-server
|
||||
```
|
||||
|
||||
⚠️ Note that Android and Linux are not 100% compatible, so use these workarounds at your own risk. Extensions that have native dependencies other than Node or that directly interact with the OS might cause issues.
|
||||
|
||||
## Extra
|
||||
|
||||
### Create a new user
|
||||
|
||||
30
flake.lock
generated
30
flake.lock
generated
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -17,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1660639432,
|
||||
"narHash": "sha256-2WDiboOCfB0LhvnDVMXOAr8ZLDfm3WdO54CkoDPwN1A=",
|
||||
"lastModified": 1683594133,
|
||||
"narHash": "sha256-iUhLhEAgOCnexSGDsYT2ouydis09uDoNzM7UC685XGE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6c6409e965a6c883677be7b9d87a95fab6c3472e",
|
||||
"rev": "8d447c5626cfefb9b129d5b30103344377fe09bc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -34,6 +37,21 @@
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
nodejs yarn' python pkg-config git rsync jq moreutils quilt bats
|
||||
nodejs yarn' python3 pkg-config git rsync jq moreutils quilt bats
|
||||
];
|
||||
buildInputs = with pkgs; (lib.optionals (!stdenv.isDarwin) [ libsecret ]
|
||||
++ (with xorg; [ libX11 libxkbfile ])
|
||||
++ lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
|
||||
++ lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
|
||||
AppKit Cocoa CoreServices Security xcbuild
|
||||
]));
|
||||
};
|
||||
|
||||
Submodule lib/vscode updated: 441438abd1...2ccd690cbf
@@ -49,7 +49,6 @@
|
||||
"@types/proxy-from-env": "^1.0.1",
|
||||
"@types/safe-compare": "^1.1.0",
|
||||
"@types/semver": "^7.1.0",
|
||||
"@types/split2": "^3.2.0",
|
||||
"@types/trusted-types": "^2.0.2",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
@@ -64,7 +63,7 @@
|
||||
"prettier": "2.8.0",
|
||||
"prettier-plugin-sh": "^0.12.8",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^4.6.2"
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
@@ -95,17 +94,16 @@
|
||||
"express": "5.0.0-alpha.8",
|
||||
"http-proxy": "^1.18.0",
|
||||
"httpolyglot": "^0.1.2",
|
||||
"i18next": "^22.4.6",
|
||||
"i18next": "^23.2.11",
|
||||
"js-yaml": "^4.0.0",
|
||||
"limiter": "^2.1.0",
|
||||
"pem": "^1.14.2",
|
||||
"proxy-agent": "^5.0.0",
|
||||
"proxy-agent": "^6.2.1",
|
||||
"qs": "6.11.0",
|
||||
"rotating-file-stream": "^3.0.0",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"safe-compare": "^1.1.4",
|
||||
"semver": "^7.1.3",
|
||||
"split2": "^4.0.0",
|
||||
"ws": "^8.0.0",
|
||||
"xdg-basedir": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/src/vs/base/common/network.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/network.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/network.ts
|
||||
@@ -166,7 +166,9 @@ class RemoteAuthoritiesImpl {
|
||||
@@ -168,7 +168,9 @@ class RemoteAuthoritiesImpl {
|
||||
return URI.from({
|
||||
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
|
||||
authority: `${host}:${port}`,
|
||||
@@ -99,26 +99,19 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactor
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
|
||||
+++ code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
|
||||
@@ -274,6 +274,7 @@ export class BrowserSocketFactory implem
|
||||
|
||||
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
|
||||
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
|
||||
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
|
||||
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
|
||||
const errorListener = socket.onError((err) => callback(err, undefined));
|
||||
socket.onOpen(() => {
|
||||
@@ -282,6 +283,3 @@ export class BrowserSocketFactory implem
|
||||
});
|
||||
}
|
||||
}
|
||||
-
|
||||
-
|
||||
-
|
||||
@@ -280,6 +280,7 @@ export class BrowserSocketFactory implem
|
||||
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
|
||||
return new Promise<ISocket>((resolve, reject) => {
|
||||
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
|
||||
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
|
||||
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
|
||||
const errorListener = socket.onError(reject);
|
||||
socket.onOpen(() => {
|
||||
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -267,12 +267,11 @@ export class WebClientServer {
|
||||
@@ -268,16 +268,15 @@ export class WebClientServer {
|
||||
return void res.end();
|
||||
}
|
||||
|
||||
@@ -127,33 +120,33 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
- return Array.isArray(val) ? val[0] : val;
|
||||
- };
|
||||
-
|
||||
- const remoteAuthority = getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host;
|
||||
const useTestResolver = (!this._environmentService.isBuilt && this._environmentService.args['use-test-resolver']);
|
||||
+ // For now we are getting the remote authority from the client to avoid
|
||||
+ // needing specific configuration for reverse proxies to work. Set this to
|
||||
+ // something invalid to make sure we catch code that is using this value
|
||||
+ // from the backend when it should not.
|
||||
+ const remoteAuthority = 'remote';
|
||||
const remoteAuthority = (
|
||||
useTestResolver
|
||||
? 'test+test'
|
||||
- : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host)
|
||||
+ : 'remote'
|
||||
);
|
||||
if (!remoteAuthority) {
|
||||
return serveError(req, res, 400, `Bad request.`);
|
||||
}
|
||||
@@ -298,6 +297,8 @@ export class WebClientServer {
|
||||
@@ -304,8 +303,12 @@ export class WebClientServer {
|
||||
scopes: [['user:email'], ['repo']]
|
||||
} : undefined;
|
||||
|
||||
+ const base = relativeRoot(getOriginalUrl(req))
|
||||
+ const vscodeBase = relativePath(getOriginalUrl(req))
|
||||
|
||||
const workbenchWebConfiguration = {
|
||||
remoteAuthority,
|
||||
@@ -309,6 +310,7 @@ export class WebClientServer {
|
||||
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
|
||||
productConfiguration: <Partial<IProductConfiguration>>{
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
+ rootEndpoint: base,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
...this._productService.extensionsGallery,
|
||||
@@ -326,8 +328,10 @@ export class WebClientServer {
|
||||
+
|
||||
const productConfiguration = <Partial<IProductConfiguration>>{
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
+ rootEndpoint: base,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
...this._productService.extensionsGallery,
|
||||
@@ -340,8 +343,10 @@ export class WebClientServer {
|
||||
const values: { [key: string]: string } = {
|
||||
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
|
||||
WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '',
|
||||
@@ -165,17 +158,17 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+ VS_BASE: vscodeBase,
|
||||
};
|
||||
|
||||
|
||||
@@ -344,7 +348,7 @@ export class WebClientServer {
|
||||
if (useTestResolver) {
|
||||
@@ -366,7 +371,7 @@ export class WebClientServer {
|
||||
'default-src \'self\';',
|
||||
'img-src \'self\' https: data: blob:;',
|
||||
'media-src \'self\';',
|
||||
- `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' http://${remoteAuthority};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
+ `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=';`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
- `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
+ `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' ${useTestResolver ? '' : ''};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
'child-src \'self\';',
|
||||
`frame-src 'self' https://*.vscode-cdn.net data:;`,
|
||||
'worker-src \'self\' data:;',
|
||||
@@ -417,3 +421,70 @@ export class WebClientServer {
|
||||
'worker-src \'self\' data: blob:;',
|
||||
@@ -439,3 +444,70 @@ export class WebClientServer {
|
||||
return void res.end(data);
|
||||
}
|
||||
}
|
||||
@@ -250,7 +243,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
@@ -32,6 +32,7 @@ export type ExtensionVirtualWorkspaceSup
|
||||
@@ -56,6 +56,7 @@ export type ExtensionVirtualWorkspaceSup
|
||||
|
||||
export interface IProductConfiguration {
|
||||
readonly codeServerVersion?: string
|
||||
@@ -262,7 +255,7 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
@@ -489,6 +489,7 @@ function doCreateUri(path: string, query
|
||||
@@ -484,6 +484,7 @@ function doCreateUri(path: string, query
|
||||
});
|
||||
}
|
||||
|
||||
@@ -270,7 +263,7 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
return URI.parse(window.location.href).with({ path, query });
|
||||
}
|
||||
|
||||
@@ -500,7 +501,7 @@ function doCreateUri(path: string, query
|
||||
@@ -495,7 +496,7 @@ function doCreateUri(path: string, query
|
||||
if (!configElement || !configElementAttribute) {
|
||||
throw new Error('Missing web configuration element');
|
||||
}
|
||||
@@ -289,12 +282,12 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
|
||||
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
-import { RemoteAuthorities } from 'vs/base/common/network';
|
||||
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { TargetPlatform } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
|
||||
@@ -75,7 +74,7 @@ export abstract class AbstractExtensionR
|
||||
public getExtensionGalleryResourceURL(galleryExtension: { publisher: string; name: string; version: string }, path?: string): URI | undefined {
|
||||
if (this._extensionGalleryResourceUrlTemplate) {
|
||||
const uri = URI.parse(format2(this._extensionGalleryResourceUrlTemplate, { publisher: galleryExtension.publisher, name: galleryExtension.name, version: galleryExtension.version, path: 'extension' }));
|
||||
@@ -102,7 +101,7 @@ export abstract class AbstractExtensionR
|
||||
: version,
|
||||
path: 'extension'
|
||||
}));
|
||||
- return this._isWebExtensionResourceEndPoint(uri) ? uri.with({ scheme: RemoteAuthorities.getPreferredWebSchema() }) : uri;
|
||||
+ return this._isWebExtensionResourceEndPoint(uri) ? URI.joinPath(URI.parse(window.location.href), uri.path) : uri;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTe
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
|
||||
@@ -100,10 +100,14 @@ class RemoteTerminalBackend extends Base
|
||||
@@ -103,10 +103,14 @@ class RemoteTerminalBackend extends Base
|
||||
}
|
||||
const reqId = e.reqId;
|
||||
const commandId = e.commandId;
|
||||
|
||||
@@ -12,7 +12,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
@@ -266,6 +266,11 @@ export interface IWorkbenchConstructionO
|
||||
@@ -288,6 +288,11 @@ export interface IWorkbenchConstructionO
|
||||
*/
|
||||
readonly userDataPath?: string
|
||||
|
||||
@@ -28,19 +28,19 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -32,6 +32,11 @@ export interface IBrowserWorkbenchEnviro
|
||||
* Options used to configure the workbench.
|
||||
*/
|
||||
@@ -34,6 +34,11 @@ export interface IBrowserWorkbenchEnviro
|
||||
readonly options?: IWorkbenchConstructionOptions;
|
||||
+
|
||||
+ /**
|
||||
|
||||
/**
|
||||
+ * Enable downloading files via menu actions.
|
||||
+ */
|
||||
+ readonly isEnabledFileDownloads?: boolean;
|
||||
}
|
||||
|
||||
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
|
||||
@@ -104,6 +109,13 @@ export class BrowserWorkbenchEnvironment
|
||||
+
|
||||
+ /**
|
||||
* Gets whether a resolver extension is expected for the environment.
|
||||
*/
|
||||
readonly expectsResolverExtension: boolean;
|
||||
@@ -111,6 +116,13 @@ export class BrowserWorkbenchEnvironment
|
||||
return this.options.userDataPath;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -94,6 +95,7 @@ export interface ServerParsedArgs {
|
||||
@@ -95,6 +96,7 @@ export interface ServerParsedArgs {
|
||||
/* ----- code-server ----- */
|
||||
'disable-update-check'?: boolean;
|
||||
'auth'?: string
|
||||
@@ -78,7 +78,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -304,6 +304,7 @@ export class WebClientServer {
|
||||
@@ -331,6 +331,7 @@ export class WebClientServer {
|
||||
remoteAuthority,
|
||||
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
|
||||
userDataPath: this._environmentService.userDataPath,
|
||||
@@ -90,29 +90,22 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
@@ -7,12 +7,11 @@ import { Event } from 'vs/base/common/ev
|
||||
@@ -7,12 +7,12 @@ import { Event } from 'vs/base/common/ev
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext } from 'vs/workbench/common/contextkeys';
|
||||
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
|
||||
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
|
||||
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext } from 'vs/workbench/common/contextkeys';
|
||||
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, ActiveEditorCanToggleReadonlyContext } from 'vs/workbench/common/contextkeys';
|
||||
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
-import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
+import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { WorkbenchState, IWorkspaceContextService, isTemporaryWorkspace } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
@@ -25,6 +24,7 @@ import { IPaneCompositePartService } fro
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
+import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
|
||||
export class WorkbenchContextKeysHandler extends Disposable {
|
||||
private inputFocusedContext: IContextKey<boolean>;
|
||||
@@ -77,7 +77,7 @@ export class WorkbenchContextKeysHandler
|
||||
@@ -80,7 +80,7 @@ export class WorkbenchContextKeysHandler
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@@ -121,7 +114,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IEditorResolverService private readonly editorResolverService: IEditorResolverService,
|
||||
@@ -202,6 +202,9 @@ export class WorkbenchContextKeysHandler
|
||||
@@ -210,6 +210,9 @@ export class WorkbenchContextKeysHandler
|
||||
this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService);
|
||||
this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART));
|
||||
|
||||
@@ -144,7 +137,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions
|
||||
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
@@ -484,13 +484,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
|
||||
@@ -489,13 +489,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
|
||||
id: DOWNLOAD_COMMAND_ID,
|
||||
title: DOWNLOAD_LABEL
|
||||
},
|
||||
@@ -172,9 +165,9 @@ Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
@@ -32,6 +32,8 @@ export const IsFullscreenContext = new R
|
||||
@@ -35,6 +35,8 @@ export const HasWebFileSystemAccess = ne
|
||||
|
||||
export const HasWebFileSystemAccess = new RawContextKey<boolean>('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access)
|
||||
export const EmbedderIdentifierContext = new RawContextKey<string | undefined>('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined'));
|
||||
|
||||
+export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true);
|
||||
+
|
||||
|
||||
@@ -14,20 +14,22 @@ We can remove this once upstream supports all language packs.
|
||||
one but is worse because it does not handle non-existent or empty files.
|
||||
7. Replace some caching and Node requires because code-server does not restart
|
||||
when changing the language unlike native Code.
|
||||
8. Make language extensions installable like normal rather than using the
|
||||
special set/clear language actions.
|
||||
|
||||
Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
@@ -220,6 +220,9 @@ export async function setupServerService
|
||||
@@ -230,6 +230,9 @@ export async function setupServerService
|
||||
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority));
|
||||
socketServer.registerChannel('extensions', channel);
|
||||
|
||||
+ const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService));
|
||||
+ socketServer.registerChannel('languagePacks', languagePackChannel);
|
||||
+
|
||||
const encryptionChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(IEncryptionMainService));
|
||||
socketServer.registerChannel('encryption', encryptionChannel);
|
||||
const credentialsChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ICredentialsMainService));
|
||||
socketServer.registerChannel('credentials', credentialsChannel);
|
||||
|
||||
Index: code-server/lib/vscode/src/vs/base/common/platform.ts
|
||||
===================================================================
|
||||
@@ -42,7 +44,7 @@ Index: code-server/lib/vscode/src/vs/base/common/platform.ts
|
||||
export const LANGUAGE_DEFAULT = 'en';
|
||||
|
||||
let _isWindows = false;
|
||||
@@ -86,17 +84,19 @@ if (typeof navigator === 'object' && !is
|
||||
@@ -90,17 +88,21 @@ if (typeof navigator === 'object' && !is
|
||||
_isMobile = _userAgent?.indexOf('Mobi') >= 0;
|
||||
_isWeb = true;
|
||||
|
||||
@@ -56,16 +58,18 @@ Index: code-server/lib/vscode/src/vs/base/common/platform.ts
|
||||
-
|
||||
- _locale = configuredLocale || LANGUAGE_DEFAULT;
|
||||
+ _locale = LANGUAGE_DEFAULT;
|
||||
|
||||
_language = _locale;
|
||||
_platformLocale = navigator.language;
|
||||
+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
|
||||
+ const rawNlsConfig = el && el.getAttribute('data-settings');
|
||||
+ if (rawNlsConfig) {
|
||||
+ try {
|
||||
+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
|
||||
+ const resolved = nlsConfig.availableLanguages['*'];
|
||||
+ _locale = nlsConfig.locale;
|
||||
+ _platformLocale = nlsConfig.osLocale;
|
||||
+ _language = resolved ? resolved : LANGUAGE_DEFAULT;
|
||||
+ _translationsConfigFile = nlsConfig._translationsConfigFile;
|
||||
+ _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
|
||||
+ } catch (error) { /* Oh well. */ }
|
||||
+ }
|
||||
}
|
||||
@@ -125,7 +129,7 @@ Index: code-server/lib/vscode/src/vs/platform/environment/common/environmentServ
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/environment/common/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/platform/environment/common/environmentService.ts
|
||||
@@ -107,7 +107,7 @@ export abstract class AbstractNativeEnvi
|
||||
@@ -101,7 +101,7 @@ export abstract class AbstractNativeEnvi
|
||||
return URI.file(join(vscodePortable, 'argv.json'));
|
||||
}
|
||||
|
||||
@@ -138,7 +142,7 @@ Index: code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
|
||||
@@ -30,6 +30,12 @@ export function getNLSConfiguration(lang
|
||||
@@ -32,6 +32,12 @@ export function getNLSConfiguration(lang
|
||||
if (InternalNLSConfiguration.is(value)) {
|
||||
value._languagePackSupport = true;
|
||||
}
|
||||
@@ -151,7 +155,7 @@ Index: code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
|
||||
return value;
|
||||
});
|
||||
_cache.set(key, result);
|
||||
@@ -44,3 +50,43 @@ export namespace InternalNLSConfiguratio
|
||||
@@ -46,3 +52,43 @@ export namespace InternalNLSConfiguratio
|
||||
return candidate && typeof candidate._languagePackId === 'string';
|
||||
}
|
||||
}
|
||||
@@ -206,24 +210,24 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
@@ -343,6 +344,8 @@ export class WebClientServer {
|
||||
callbackRoute: this._callbackRoute
|
||||
};
|
||||
|
||||
@@ -299,6 +300,8 @@ export class WebClientServer {
|
||||
|
||||
const base = relativeRoot(getOriginalUrl(req))
|
||||
const vscodeBase = relativePath(getOriginalUrl(req))
|
||||
+ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath);
|
||||
+ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)
|
||||
|
||||
const workbenchWebConfiguration = {
|
||||
remoteAuthority,
|
||||
@@ -336,6 +339,7 @@ export class WebClientServer {
|
||||
const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl;
|
||||
const values: { [key: string]: string } = {
|
||||
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
|
||||
@@ -351,6 +354,7 @@ export class WebClientServer {
|
||||
WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''),
|
||||
BASE: base,
|
||||
VS_BASE: vscodeBase,
|
||||
+ NLS_CONFIGURATION: asJSON(nlsConfiguration),
|
||||
};
|
||||
|
||||
|
||||
if (useTestResolver) {
|
||||
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
@@ -236,7 +240,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -96,6 +97,7 @@ export interface ServerParsedArgs {
|
||||
@@ -97,6 +98,7 @@ export interface ServerParsedArgs {
|
||||
'disable-update-check'?: boolean;
|
||||
'auth'?: string
|
||||
'disable-file-downloads'?: boolean;
|
||||
@@ -248,9 +252,18 @@ Index: code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
||||
@@ -119,8 +119,9 @@ import 'vs/workbench/contrib/logs/browse
|
||||
// Explorer
|
||||
import 'vs/workbench/contrib/files/browser/files.web.contribution';
|
||||
@@ -52,7 +52,7 @@ import 'vs/workbench/services/dialogs/br
|
||||
import 'vs/workbench/services/host/browser/browserHostService';
|
||||
import 'vs/workbench/services/lifecycle/browser/lifecycleService';
|
||||
import 'vs/workbench/services/clipboard/browser/clipboardService';
|
||||
-import 'vs/workbench/services/localization/browser/localeService';
|
||||
+import 'vs/workbench/services/localization/electron-sandbox/localeService';
|
||||
import 'vs/workbench/services/path/browser/pathService';
|
||||
import 'vs/workbench/services/themes/browser/browserHostColorSchemeService';
|
||||
import 'vs/workbench/services/encryption/browser/encryptionService';
|
||||
@@ -117,8 +117,9 @@ registerSingleton(ILanguagePackService,
|
||||
// Logs
|
||||
import 'vs/workbench/contrib/logs/browser/logs.contribution';
|
||||
|
||||
-// Localization
|
||||
-import 'vs/workbench/contrib/localization/browser/localization.contribution';
|
||||
@@ -264,9 +277,9 @@ Index: code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePack
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
|
||||
+++ code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
|
||||
@@ -6,18 +6,24 @@
|
||||
@@ -5,18 +5,24 @@
|
||||
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Language } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
+import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
@@ -289,8 +302,8 @@ Index: code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePack
|
||||
+ this.languagePackService = ProxyChannel.toService<ILanguagePackService>(remoteAgentService.getConnection()!.getChannel('languagePacks'))
|
||||
}
|
||||
|
||||
async getBuiltInExtensionTranslationsUri(id: string): Promise<URI | undefined> {
|
||||
@@ -73,6 +79,6 @@ export class WebLanguagePacksService ext
|
||||
async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise<URI | undefined> {
|
||||
@@ -72,6 +78,6 @@ export class WebLanguagePacksService ext
|
||||
|
||||
// Web doesn't have a concept of language packs, so we just return an empty array
|
||||
getInstalledLanguages(): Promise<ILanguagePackItem[]> {
|
||||
@@ -298,11 +311,11 @@ Index: code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePack
|
||||
+ return this.languagePackService.getInstalledLanguages()
|
||||
}
|
||||
}
|
||||
Index: code-server/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
|
||||
Index: code-server/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
|
||||
@@ -41,7 +41,8 @@ export class NativeLocaleService impleme
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts
|
||||
@@ -51,7 +51,8 @@ class NativeLocaleService implements ILo
|
||||
@IProductService private readonly productService: IProductService
|
||||
) { }
|
||||
|
||||
@@ -312,7 +325,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/localization/electron-san
|
||||
try {
|
||||
const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' });
|
||||
|
||||
@@ -68,9 +69,6 @@ export class NativeLocaleService impleme
|
||||
@@ -78,9 +79,6 @@ class NativeLocaleService implements ILo
|
||||
}
|
||||
|
||||
private async writeLocaleValue(locale: string | undefined): Promise<boolean> {
|
||||
@@ -322,3 +335,70 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/localization/electron-san
|
||||
await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true);
|
||||
return true;
|
||||
}
|
||||
Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
|
||||
@@ -337,9 +337,6 @@ export class InstallAction extends Exten
|
||||
if (this.extension.isBuiltin) {
|
||||
return;
|
||||
}
|
||||
- if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
|
||||
- return;
|
||||
- }
|
||||
if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) {
|
||||
this.enabled = this.options.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion;
|
||||
this.updateLabel();
|
||||
@@ -607,7 +604,7 @@ export abstract class InstallInOtherServ
|
||||
}
|
||||
|
||||
if (isLanguagePackExtension(this.extension.local.manifest)) {
|
||||
- return true;
|
||||
+ return false;
|
||||
}
|
||||
|
||||
// Prefers to run on UI
|
||||
@@ -1695,17 +1692,6 @@ export class SetLanguageAction extends E
|
||||
update(): void {
|
||||
this.enabled = false;
|
||||
this.class = SetLanguageAction.DisabledClass;
|
||||
- if (!this.extension) {
|
||||
- return;
|
||||
- }
|
||||
- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
|
||||
- return;
|
||||
- }
|
||||
- if (this.extension.gallery && language === getLocale(this.extension.gallery)) {
|
||||
- return;
|
||||
- }
|
||||
- this.enabled = true;
|
||||
- this.class = SetLanguageAction.EnabledClass;
|
||||
}
|
||||
|
||||
override async run(): Promise<any> {
|
||||
@@ -1722,7 +1708,6 @@ export class ClearLanguageAction extends
|
||||
private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`;
|
||||
|
||||
constructor(
|
||||
- @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@ILocaleService private readonly localeService: ILocaleService,
|
||||
) {
|
||||
super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
|
||||
@@ -1732,17 +1717,6 @@ export class ClearLanguageAction extends
|
||||
update(): void {
|
||||
this.enabled = false;
|
||||
this.class = ClearLanguageAction.DisabledClass;
|
||||
- if (!this.extension) {
|
||||
- return;
|
||||
- }
|
||||
- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
|
||||
- return;
|
||||
- }
|
||||
- if (this.extension.gallery && language !== getLocale(this.extension.gallery)) {
|
||||
- return;
|
||||
- }
|
||||
- this.enabled = true;
|
||||
- this.class = ClearLanguageAction.EnabledClass;
|
||||
}
|
||||
|
||||
override async run(): Promise<any> {
|
||||
|
||||
@@ -10,16 +10,25 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
|
||||
@@ -60,7 +60,7 @@ import { GettingStartedIndexList } from
|
||||
@@ -10,7 +10,7 @@ import { IInstantiationService } from 'v
|
||||
import { IEditorSerializer, IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
-import { $, addDisposableListener, append, clearNode, Dimension, reset } from 'vs/base/browser/dom';
|
||||
+import { $, addDisposableListener, append, clearNode, Dimension, reset, prepend } from 'vs/base/browser/dom';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { hiddenEntriesConfigurationKey, IResolvedWalkthrough, IResolvedWalkthroughStep, IWalkthroughsService } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService';
|
||||
@@ -59,7 +59,7 @@ import { GettingStartedIndexList } from
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
-import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
|
||||
+import { IsEnabledCoderGettingStarted, WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
|
||||
import { OpenFolderViaWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { OpenFolderAction, OpenFileFolderAction, OpenFolderViaWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
import { OpenRecentAction } from 'vs/workbench/browser/actions/windowActions';
|
||||
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
|
||||
@@ -759,6 +759,72 @@ export class GettingStartedPage extends
|
||||
@@ -782,6 +782,72 @@ export class GettingStartedPage extends
|
||||
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))
|
||||
);
|
||||
|
||||
@@ -87,38 +96,21 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
|
||||
+ ),
|
||||
+ ),
|
||||
+ );
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
|
||||
const leftColumn = $('.categories-column.categories-column-left', {},);
|
||||
const rightColumn = $('.categories-column.categories-column-right', {},);
|
||||
@@ -776,13 +842,23 @@ export class GettingStartedPage extends
|
||||
const layoutLists = () => {
|
||||
if (gettingStartedList.itemCount) {
|
||||
this.container.classList.remove('noWalkthroughs');
|
||||
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
- reset(rightColumn, gettingStartedList.getDomElement());
|
||||
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), gettingStartedList.getDomElement());
|
||||
+ reset(rightColumn, gettingStartedCoder);
|
||||
+ } else {
|
||||
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
+ reset(rightColumn, gettingStartedList.getDomElement());
|
||||
+ }
|
||||
+
|
||||
|
||||
@@ -831,6 +897,9 @@ export class GettingStartedPage extends
|
||||
recentList.setLimit(5);
|
||||
reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
|
||||
}
|
||||
else {
|
||||
this.container.classList.add('noWalkthroughs');
|
||||
- reset(leftColumn, startList.getDomElement());
|
||||
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
|
||||
+ reset(leftColumn, startList.getDomElement(), gettingStartedCoder);
|
||||
+ } else {
|
||||
+ reset(leftColumn, startList.getDomElement());
|
||||
+ }
|
||||
reset(rightColumn, recentList.getDomElement());
|
||||
recentList.setLimit(10);
|
||||
}
|
||||
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
|
||||
+ prepend(rightColumn, gettingStartedCoder)
|
||||
+ }
|
||||
};
|
||||
|
||||
featuredExtensionList.onDidChange(layoutFeaturedExtension);
|
||||
Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
|
||||
@@ -143,7 +135,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
@@ -271,6 +271,11 @@ export interface IWorkbenchConstructionO
|
||||
@@ -293,6 +293,11 @@ export interface IWorkbenchConstructionO
|
||||
*/
|
||||
readonly isEnabledFileDownloads?: boolean
|
||||
|
||||
@@ -159,19 +151,19 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -37,6 +37,11 @@ export interface IBrowserWorkbenchEnviro
|
||||
* Enable downloading files via menu actions.
|
||||
*/
|
||||
@@ -39,6 +39,11 @@ export interface IBrowserWorkbenchEnviro
|
||||
readonly isEnabledFileDownloads?: boolean;
|
||||
+
|
||||
+ /**
|
||||
|
||||
/**
|
||||
+ * Enable Coder's custom getting started text.
|
||||
+ */
|
||||
+ readonly isEnabledCoderGettingStarted?: boolean;
|
||||
}
|
||||
|
||||
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
|
||||
@@ -116,6 +121,13 @@ export class BrowserWorkbenchEnvironment
|
||||
+
|
||||
+ /**
|
||||
* Gets whether a resolver extension is expected for the environment.
|
||||
*/
|
||||
readonly expectsResolverExtension: boolean;
|
||||
@@ -123,6 +128,13 @@ export class BrowserWorkbenchEnvironment
|
||||
return this.options.isEnabledFileDownloads;
|
||||
}
|
||||
|
||||
@@ -197,11 +189,11 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -98,6 +99,7 @@ export interface ServerParsedArgs {
|
||||
@@ -99,6 +100,7 @@ export interface ServerParsedArgs {
|
||||
'auth'?: string
|
||||
'disable-file-downloads'?: boolean;
|
||||
'locale'?: string
|
||||
+ 'disable-getting-started-override'?: boolean;
|
||||
+ 'disable-getting-started-override'?: boolean,
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -209,7 +201,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -308,6 +308,7 @@ export class WebClientServer {
|
||||
@@ -334,6 +334,7 @@ export class WebClientServer {
|
||||
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
|
||||
userDataPath: this._environmentService.userDataPath,
|
||||
isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'],
|
||||
@@ -223,14 +215,14 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
|
||||
@@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/ev
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
|
||||
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, IsEnabledCoderGettingStarted } from 'vs/workbench/common/contextkeys';
|
||||
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
|
||||
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, ActiveEditorCanToggleReadonlyContext } from 'vs/workbench/common/contextkeys';
|
||||
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, IsEnabledCoderGettingStarted, ActiveEditorCanToggleReadonlyContext } from 'vs/workbench/common/contextkeys';
|
||||
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
@@ -204,6 +204,7 @@ export class WorkbenchContextKeysHandler
|
||||
@@ -212,6 +212,7 @@ export class WorkbenchContextKeysHandler
|
||||
|
||||
// code-server
|
||||
IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true)
|
||||
@@ -242,8 +234,8 @@ Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
|
||||
@@ -33,6 +33,7 @@ export const IsFullscreenContext = new R
|
||||
export const HasWebFileSystemAccess = new RawContextKey<boolean>('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access)
|
||||
@@ -36,6 +36,7 @@ export const HasWebFileSystemAccess = ne
|
||||
export const EmbedderIdentifierContext = new RawContextKey<string | undefined>('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined'));
|
||||
|
||||
export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true);
|
||||
+export const IsEnabledCoderGettingStarted = new RawContextKey<boolean>('isEnabledCoderGettingStarted', true, true);
|
||||
|
||||
@@ -95,7 +95,7 @@ Index: code-server/lib/vscode/src/vs/base/common/processes.ts
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/processes.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/processes.ts
|
||||
@@ -111,6 +111,8 @@ export function sanitizeProcessEnvironme
|
||||
/^VSCODE_(?!(PORTABLE|SHELL_LOGIN)).+$/,
|
||||
/^VSCODE_(?!(PORTABLE|SHELL_LOGIN|ENV_REPLACE|ENV_APPEND|ENV_PREPEND)).+$/,
|
||||
/^SNAP(|_.*)$/,
|
||||
/^GDK_PIXBUF_.+$/,
|
||||
+ /^CODE_SERVER_.+$/,
|
||||
@@ -107,7 +107,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandl
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts
|
||||
@@ -145,8 +145,11 @@ export class BrowserDialogHandler implem
|
||||
@@ -77,8 +77,11 @@ export class BrowserDialogHandler extend
|
||||
|
||||
async about(): Promise<void> {
|
||||
const detailString = (useAgo: boolean): string => {
|
||||
@@ -176,7 +176,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
@@ -69,6 +69,7 @@ import { IndexedDB } from 'vs/base/brows
|
||||
@@ -67,6 +67,7 @@ import { IndexedDB } from 'vs/base/brows
|
||||
import { BrowserCredentialsService } from 'vs/workbench/services/credentials/browser/credentialsService';
|
||||
import { IWorkspace } from 'vs/workbench/services/host/browser/browserHostService';
|
||||
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
|
||||
@@ -184,7 +184,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel';
|
||||
@@ -119,6 +120,9 @@ export class BrowserMain extends Disposa
|
||||
@@ -127,6 +128,9 @@ export class BrowserMain extends Disposa
|
||||
// Startup
|
||||
const instantiationService = workbench.startup();
|
||||
|
||||
@@ -198,7 +198,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
@@ -31,6 +31,8 @@ export type ExtensionVirtualWorkspaceSup
|
||||
@@ -55,6 +55,8 @@ export type ExtensionVirtualWorkspaceSup
|
||||
};
|
||||
|
||||
export interface IProductConfiguration {
|
||||
@@ -264,11 +264,11 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -308,6 +308,7 @@ export class WebClientServer {
|
||||
folderUri: resolveWorkspaceURI(this._environmentService.args['default-folder']),
|
||||
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
|
||||
productConfiguration: <Partial<IProductConfiguration>>{
|
||||
+ codeServerVersion: this._productService.codeServerVersion,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
...this._productService.extensionsGallery,
|
||||
@@ -305,6 +305,7 @@ export class WebClientServer {
|
||||
} : undefined;
|
||||
|
||||
const productConfiguration = <Partial<IProductConfiguration>>{
|
||||
+ codeServerVersion: this._productService.codeServerVersion,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
...this._productService.extensionsGallery,
|
||||
|
||||
@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -303,6 +303,7 @@ export class WebClientServer {
|
||||
@@ -326,6 +326,7 @@ export class WebClientServer {
|
||||
const workbenchWebConfiguration = {
|
||||
remoteAuthority,
|
||||
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
|
||||
@@ -32,7 +32,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
|
||||
@@ -261,6 +261,11 @@ export interface IWorkbenchConstructionO
|
||||
@@ -283,6 +283,11 @@ export interface IWorkbenchConstructionO
|
||||
*/
|
||||
readonly configurationDefaults?: Record<string, any>;
|
||||
|
||||
@@ -48,7 +48,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -95,7 +95,14 @@ export class BrowserWorkbenchEnvironment
|
||||
@@ -102,7 +102,14 @@ export class BrowserWorkbenchEnvironment
|
||||
get logFile(): URI { return joinPath(this.windowLogsPath, 'window.log'); }
|
||||
|
||||
@memoize
|
||||
|
||||
@@ -8,7 +8,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
@@ -34,6 +34,7 @@ export interface IProductConfiguration {
|
||||
@@ -58,6 +58,7 @@ export interface IProductConfiguration {
|
||||
readonly codeServerVersion?: string
|
||||
readonly rootEndpoint?: string
|
||||
readonly updateEndpoint?: string
|
||||
@@ -28,7 +28,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -92,6 +93,7 @@ export const serverOptions: OptionDescri
|
||||
@@ -93,6 +94,7 @@ export const serverOptions: OptionDescri
|
||||
export interface ServerParsedArgs {
|
||||
/* ----- code-server ----- */
|
||||
'disable-update-check'?: boolean;
|
||||
@@ -40,14 +40,14 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -313,6 +313,7 @@ export class WebClientServer {
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
+ logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
@@ -310,6 +310,7 @@ export class WebClientServer {
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
+ logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts
|
||||
|
||||
@@ -40,7 +40,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -111,7 +111,7 @@ export class WebClientServer {
|
||||
@@ -112,7 +112,7 @@ export class WebClientServer {
|
||||
const serverRootPath = getRemoteServerRootPath(_productService);
|
||||
this._staticRoute = `${serverRootPath}/static`;
|
||||
this._callbackRoute = `${serverRootPath}/callback`;
|
||||
@@ -49,22 +49,22 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,14 +312,7 @@ export class WebClientServer {
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
embedderIdentifier: 'server-distro',
|
||||
- extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
- ...this._productService.extensionsGallery,
|
||||
- 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({
|
||||
- scheme: 'http',
|
||||
- authority: remoteAuthority,
|
||||
- path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}`
|
||||
- }).toString(true)
|
||||
- } : undefined
|
||||
+ extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
callbackRoute: this._callbackRoute
|
||||
@@ -310,14 +310,7 @@ export class WebClientServer {
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
embedderIdentifier: 'server-distro',
|
||||
- extensionsGallery: this._webExtensionResourceUrlTemplate ? {
|
||||
- ...this._productService.extensionsGallery,
|
||||
- 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({
|
||||
- scheme: 'http',
|
||||
- authority: remoteAuthority,
|
||||
- path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}`
|
||||
- }).toString(true)
|
||||
- } : undefined
|
||||
+ extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
|
||||
if (!this._environmentService.isBuilt) {
|
||||
Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
|
||||
@@ -74,10 +74,10 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
|
||||
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
-import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { TargetPlatform } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
|
||||
|
||||
@@ -60,7 +59,7 @@ export abstract class AbstractExtensionR
|
||||
const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
|
||||
@@ -77,7 +76,7 @@ export abstract class AbstractExtensionR
|
||||
private readonly _environmentService: IEnvironmentService,
|
||||
private readonly _configurationService: IConfigurationService,
|
||||
) {
|
||||
|
||||
@@ -6,24 +6,11 @@ https://github.com/microsoft/vscode-extension-samples/tree/ddae6c0c9ff203b4ed6f6
|
||||
We also override isProposedApiEnabled in case an extension does not declare the
|
||||
APIs it needs correctly (the Jupyter extension had this issue).
|
||||
|
||||
Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
|
||||
@@ -1486,7 +1486,7 @@ class ProposedApiController {
|
||||
|
||||
this._envEnabledExtensions = new Set((_environmentService.extensionEnabledProposedApi ?? []).map(id => ExtensionIdentifier.toKey(id)));
|
||||
|
||||
- this._envEnablesProposedApiForAll =
|
||||
+ this._envEnablesProposedApiForAll = true ||
|
||||
!_environmentService.isBuilt || // always allow proposed API when running out of sources
|
||||
(_environmentService.isExtensionDevelopment && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension
|
||||
(this._envEnabledExtensions.size === 0 && Array.isArray(_environmentService.extensionEnabledProposedApi)); // always allow proposed API if --enable-proposed-api is provided without extension ID
|
||||
Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
|
||||
@@ -364,10 +364,7 @@ function extensionDescriptionArrayToMap(
|
||||
@@ -260,10 +260,7 @@ function extensionDescriptionArrayToMap(
|
||||
}
|
||||
|
||||
export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {
|
||||
@@ -35,3 +22,16 @@ Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extens
|
||||
}
|
||||
|
||||
export function checkProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): void {
|
||||
Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts
|
||||
@@ -24,7 +24,7 @@ export class ExtensionsProposedApi {
|
||||
|
||||
this._envEnabledExtensions = new Set((_environmentService.extensionEnabledProposedApi ?? []).map(id => ExtensionIdentifier.toKey(id)));
|
||||
|
||||
- this._envEnablesProposedApiForAll =
|
||||
+ this._envEnablesProposedApiForAll = true ||
|
||||
!_environmentService.isBuilt || // always allow proposed API when running out of sources
|
||||
(_environmentService.isExtensionDevelopment && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension
|
||||
(this._envEnabledExtensions.size === 0 && Array.isArray(_environmentService.extensionEnabledProposedApi)); // always allow proposed API if --enable-proposed-api is provided without extension ID
|
||||
|
||||
@@ -30,7 +30,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
@@ -35,6 +35,7 @@ export interface IProductConfiguration {
|
||||
@@ -59,6 +59,7 @@ export interface IProductConfiguration {
|
||||
readonly rootEndpoint?: string
|
||||
readonly updateEndpoint?: string
|
||||
readonly logoutEndpoint?: string
|
||||
@@ -42,17 +42,8 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts
|
||||
+++ code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts
|
||||
@@ -11,7 +11,7 @@ import { StopWatch } from 'vs/base/commo
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
-import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
+import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolvedOptions, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts';
|
||||
|
||||
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
|
||||
@@ -29,7 +29,7 @@ export class RemoteAuthorityResolverServ
|
||||
constructor(
|
||||
@@ -33,7 +33,7 @@ export class RemoteAuthorityResolverServ
|
||||
isWorkbenchOptionsBasedResolution: boolean,
|
||||
connectionToken: Promise<string> | string | undefined,
|
||||
resourceUriProvider: ((uri: URI) => URI) | undefined,
|
||||
- @IProductService productService: IProductService,
|
||||
@@ -60,7 +51,7 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
@@ -75,9 +75,14 @@ export class RemoteAuthorityResolverServ
|
||||
@@ -84,9 +84,14 @@ export class RemoteAuthorityResolverServ
|
||||
const connectionToken = await Promise.resolve(this._connectionTokens.get(authority) || this._connectionToken);
|
||||
performance.mark(`code/didResolveConnectionToken/${authorityPrefix}`);
|
||||
this._logService.info(`Resolved connection token (${authorityPrefix}) after ${sw.elapsed()} ms`);
|
||||
@@ -71,28 +62,28 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
|
||||
+ }
|
||||
const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80);
|
||||
const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort);
|
||||
- const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken } };
|
||||
+ const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken }, options };
|
||||
RemoteAuthorities.set(authority, result.authority.host, result.authority.port);
|
||||
- const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken } };
|
||||
+ const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken }, options };
|
||||
RemoteAuthorities.set(authority, host, port);
|
||||
this._cache.set(authority, result);
|
||||
this._onDidChangeConnectionData.fire();
|
||||
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -314,6 +314,7 @@ export class WebClientServer {
|
||||
rootEndpoint: base,
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
+ proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
@@ -311,6 +311,7 @@ export class WebClientServer {
|
||||
rootEndpoint: base,
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
+ proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
Index: code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
|
||||
@@ -383,7 +383,7 @@ export async function createTerminalEnvi
|
||||
@@ -271,7 +271,7 @@ export async function createTerminalEnvi
|
||||
|
||||
// Sanitize the environment, removing any undesirable VS Code and Electron environment
|
||||
// variables
|
||||
@@ -113,25 +104,26 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
|
||||
|
||||
interface ICredential {
|
||||
service: string;
|
||||
@@ -511,6 +512,38 @@ function doCreateUri(path: string, query
|
||||
} : undefined,
|
||||
@@ -505,6 +506,39 @@ function doCreateUri(path: string, query
|
||||
settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined,
|
||||
workspaceProvider: WorkspaceProvider.create(config),
|
||||
urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute),
|
||||
- credentialsProvider: config.remoteAuthority ? undefined : new LocalStorageCredentialsProvider() // with a remote, we don't use a local credentials provider
|
||||
+ credentialsProvider: config.remoteAuthority ? undefined : new LocalStorageCredentialsProvider(), // with a remote, we don't use a local credentials provider
|
||||
- credentialsProvider: config.remoteAuthority ? undefined /* with a remote, we don't use a local credentials provider */ : new LocalStorageCredentialsProvider()
|
||||
+ credentialsProvider: config.remoteAuthority ? undefined /* with a remote, we don't use a local credentials provider */ : new LocalStorageCredentialsProvider(),
|
||||
+ resolveExternalUri: (uri: URI): Promise<URI> => {
|
||||
+ let resolvedUri = uri
|
||||
+ const localhostMatch = extractLocalHostUriMetaDataForPortMapping(resolvedUri)
|
||||
+
|
||||
+ if (localhostMatch && resolvedUri.authority !== location.host) {
|
||||
+ if (config.productConfiguration && config.productConfiguration.proxyEndpointTemplate) {
|
||||
+ resolvedUri = URI.parse(new URL(config.productConfiguration.proxyEndpointTemplate.replace('{{port}}', localhostMatch.port.toString()), window.location.href).toString())
|
||||
+ const renderedTemplate = config.productConfiguration.proxyEndpointTemplate
|
||||
+ .replace('{{port}}', localhostMatch.port.toString())
|
||||
+ .replace('{{host}}', window.location.host)
|
||||
+ resolvedUri = URI.parse(new URL(renderedTemplate, window.location.href).toString())
|
||||
+ } else {
|
||||
+ throw new Error(`Failed to resolve external URI: ${uri.toString()}. Could not determine base url because productConfiguration missing.`)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // If not localhost, return unmodified
|
||||
+ // If not localhost, return unmodified.
|
||||
+ return Promise.resolve(resolvedUri)
|
||||
+ },
|
||||
+ tunnelProvider: {
|
||||
@@ -157,7 +149,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExpl
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
|
||||
@@ -73,7 +73,7 @@ export class ForwardedPortsView extends
|
||||
@@ -74,7 +74,7 @@ export class ForwardedPortsView extends
|
||||
this.contextKeyListener = undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
@@ -36,6 +36,10 @@ export interface IProductConfiguration {
|
||||
@@ -60,6 +60,10 @@ export interface IProductConfiguration {
|
||||
readonly updateEndpoint?: string
|
||||
readonly logoutEndpoint?: string
|
||||
readonly proxyEndpointTemplate?: string
|
||||
@@ -54,14 +54,14 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -316,6 +316,10 @@ export class WebClientServer {
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
|
||||
+ serviceWorker: {
|
||||
+ scope: vscodeBase + '/',
|
||||
+ path: base + '/_static/out/browser/serviceWorker.js',
|
||||
+ },
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
@@ -312,6 +312,10 @@ export class WebClientServer {
|
||||
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
|
||||
proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
|
||||
+ serviceWorker: {
|
||||
+ scope: vscodeBase + '/',
|
||||
+ path: base + '/_static/out/browser/serviceWorker.js',
|
||||
+ },
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/build/gulpfile.reh.js
|
||||
+++ code-server/lib/vscode/build/gulpfile.reh.js
|
||||
@@ -192,8 +192,7 @@ function packageTask(type, platform, arc
|
||||
@@ -242,8 +242,7 @@ function packageTask(type, platform, arc
|
||||
|
||||
const src = gulp.src(sourceFolderName + '/**', { base: '.' })
|
||||
.pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); }))
|
||||
@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
|
||||
|
||||
const workspaceExtensionPoints = ['debuggers', 'jsonValidation'];
|
||||
const isUIExtension = (manifest) => {
|
||||
@@ -232,9 +231,9 @@ function packageTask(type, platform, arc
|
||||
@@ -282,9 +281,9 @@ function packageTask(type, platform, arc
|
||||
.map(name => `.build/extensions/${name}/**`);
|
||||
|
||||
const extensions = gulp.src(extensionPaths, { base: '.build', dot: true });
|
||||
@@ -32,7 +32,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
|
||||
|
||||
let version = packageJson.version;
|
||||
const quality = product.quality;
|
||||
@@ -389,7 +388,7 @@ function tweakProductForServerWeb(produc
|
||||
@@ -431,7 +430,7 @@ function tweakProductForServerWeb(produc
|
||||
const minifyTask = task.define(`minify-vscode-${type}`, task.series(
|
||||
optimizeTask,
|
||||
util.rimraf(`out-vscode-${type}-min`),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Store a static reference to the IPC socket
|
||||
Store the IPC socket with workspace metadata.
|
||||
|
||||
This lets us use it to open files inside code-server from outside of
|
||||
code-server.
|
||||
@@ -9,29 +9,121 @@ To test this:
|
||||
|
||||
It should open in your existing code-server instance.
|
||||
|
||||
When the extension host is terminated, the socket is unregistered.
|
||||
|
||||
Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
|
||||
@@ -2,7 +2,9 @@
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-
|
||||
+import { promises as fs } from 'fs';
|
||||
+import * as os from 'os'
|
||||
+import * as path from 'vs/base/common/path';
|
||||
+import * as _http from 'http';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
|
||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||
@@ -72,6 +74,10 @@ export class ExtHostExtensionService ext
|
||||
if (this._initData.remote.isRemote && this._initData.remote.authority) {
|
||||
const cliServer = this._instaService.createInstance(CLIServer);
|
||||
process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath;
|
||||
+
|
||||
+ fs.writeFile(path.join(os.tmpdir(), 'vscode-ipc'), cliServer.ipcHandlePath).catch((error) => {
|
||||
+ this._logService.error(error);
|
||||
+ });
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { ExtensionRuntime } from 'vs/wor
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { realpathSync } from 'vs/base/node/extpath';
|
||||
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder';
|
||||
+import { IExtHostWorkspace } from '../common/extHostWorkspace';
|
||||
import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider';
|
||||
|
||||
// Module loading tricks
|
||||
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||
@@ -83,6 +84,52 @@ export class ExtHostExtensionService ext
|
||||
await interceptor.install();
|
||||
performance.mark('code/extHost/didInitAPI');
|
||||
|
||||
+ (async () => {
|
||||
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
|
||||
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
|
||||
+ if (!socketPath || !codeServerSocketPath) {
|
||||
+ return;
|
||||
+ }
|
||||
+ const workspace = this._instaService.invokeFunction((accessor) => {
|
||||
+ const workspaceService = accessor.get(IExtHostWorkspace);
|
||||
+ return workspaceService.workspace;
|
||||
+ });
|
||||
+ const entry = {
|
||||
+ workspace,
|
||||
+ socketPath
|
||||
+ };
|
||||
+ const message = JSON.stringify({entry});
|
||||
+ await new Promise<void>((resolve, reject) => {
|
||||
+ const opts: _http.RequestOptions = {
|
||||
+ path: '/add-session',
|
||||
+ socketPath: codeServerSocketPath,
|
||||
+ method: 'POST',
|
||||
+ headers: {
|
||||
+ 'content-type': 'application/json',
|
||||
+ }
|
||||
+ };
|
||||
+ const req = _http.request(opts, (res) => {
|
||||
+ res.on('error', reject);
|
||||
+ res.on('end', () => {
|
||||
+ try {
|
||||
+ if (res.statusCode === 200) {
|
||||
+ resolve();
|
||||
+ } else {
|
||||
+ reject(new Error('Unexpected status code: ' + res.statusCode));
|
||||
+ }
|
||||
+ } catch (e: unknown) {
|
||||
+ reject(e);
|
||||
+ }
|
||||
+ });
|
||||
+ });
|
||||
+ req.on('error', reject);
|
||||
+ req.write(message);
|
||||
+ req.end();
|
||||
+ });
|
||||
+ })().catch(error => {
|
||||
+ this._logService.error(error);
|
||||
+ });
|
||||
+
|
||||
// Do this when extension service exists, but extensions are not being activated yet.
|
||||
const configProvider = await this._extHostConfiguration.getConfigProvider();
|
||||
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
|
||||
Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
+import * as _http from 'http';
|
||||
import * as nativeWatchdog from 'native-watchdog';
|
||||
import * as net from 'net';
|
||||
import * as minimist from 'minimist';
|
||||
@@ -400,7 +401,28 @@ async function startExtensionHostProcess
|
||||
);
|
||||
|
||||
// rewrite onTerminate-function to be a proper shutdown
|
||||
- onTerminate = (reason: string) => extensionHostMain.terminate(reason);
|
||||
+ onTerminate = (reason: string) => {
|
||||
+ extensionHostMain.terminate(reason);
|
||||
+
|
||||
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
|
||||
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
|
||||
+ if (!socketPath || !codeServerSocketPath) {
|
||||
+ return;
|
||||
+ }
|
||||
+ const message = JSON.stringify({socketPath});
|
||||
+ const opts: _http.RequestOptions = {
|
||||
+ path: '/delete-session',
|
||||
+ socketPath: codeServerSocketPath,
|
||||
+ method: 'POST',
|
||||
+ headers: {
|
||||
+ 'content-type': 'application/json',
|
||||
+ 'accept': 'application/json'
|
||||
+ }
|
||||
+ };
|
||||
+ const req = _http.request(opts);
|
||||
+ req.write(message);
|
||||
+ req.end();
|
||||
+ };
|
||||
}
|
||||
|
||||
startExtensionHostProcess().catch((err) => console.log(err));
|
||||
|
||||
@@ -12,7 +12,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
@@ -70,6 +70,7 @@ import { IExtensionsScannerService } fro
|
||||
@@ -67,6 +67,7 @@ import { IExtensionsScannerService } fro
|
||||
import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService';
|
||||
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
|
||||
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
@@ -20,8 +20,8 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
||||
import { NullPolicyService } from 'vs/platform/policy/common/policy';
|
||||
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
@@ -142,10 +143,13 @@ export async function setupServerService
|
||||
const machineId = await getMachineId();
|
||||
@@ -149,10 +150,13 @@ export async function setupServerService
|
||||
let oneDsAppender: ITelemetryAppender = NullAppender;
|
||||
const isInternal = isInternalTelemetry(productService, configurationService);
|
||||
if (supportsTelemetry(productService, environmentService)) {
|
||||
- if (productService.aiConfig && productService.aiConfig.ariaKey) {
|
||||
@@ -94,11 +94,11 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -321,6 +321,7 @@ export class WebClientServer {
|
||||
scope: vscodeBase + '/',
|
||||
path: base + '/_static/out/browser/serviceWorker.js',
|
||||
},
|
||||
+ enableTelemetry: this._productService.enableTelemetry,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
@@ -316,6 +316,7 @@ export class WebClientServer {
|
||||
scope: vscodeBase + '/',
|
||||
path: base + '/_static/out/browser/serviceWorker.js',
|
||||
},
|
||||
+ enableTelemetry: this._productService.enableTelemetry,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
|
||||
@@ -93,7 +93,7 @@ Index: code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/base/common/product.ts
|
||||
+++ code-server/lib/vscode/src/vs/base/common/product.ts
|
||||
@@ -33,6 +33,7 @@ export type ExtensionVirtualWorkspaceSup
|
||||
@@ -57,6 +57,7 @@ export type ExtensionVirtualWorkspaceSup
|
||||
export interface IProductConfiguration {
|
||||
readonly codeServerVersion?: string
|
||||
readonly rootEndpoint?: string
|
||||
@@ -105,14 +105,14 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -312,6 +312,7 @@ export class WebClientServer {
|
||||
productConfiguration: <Partial<IProductConfiguration>>{
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
+ updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
},
|
||||
@@ -309,6 +309,7 @@ export class WebClientServer {
|
||||
const productConfiguration = <Partial<IProductConfiguration>>{
|
||||
codeServerVersion: this._productService.codeServerVersion,
|
||||
rootEndpoint: base,
|
||||
+ updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
|
||||
embedderIdentifier: 'server-distro',
|
||||
extensionsGallery: this._productService.extensionsGallery,
|
||||
};
|
||||
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
@@ -126,7 +126,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
||||
|
||||
/* ----- server setup ----- */
|
||||
|
||||
@@ -88,6 +90,8 @@ export const serverOptions: OptionDescri
|
||||
@@ -89,6 +91,8 @@ export const serverOptions: OptionDescri
|
||||
};
|
||||
|
||||
export interface ServerParsedArgs {
|
||||
|
||||
@@ -41,7 +41,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
|
||||
@@ -224,7 +224,7 @@ export class BrowserWorkbenchEnvironment
|
||||
@@ -225,7 +225,7 @@ export class BrowserWorkbenchEnvironment
|
||||
|
||||
@memoize
|
||||
get webviewExternalEndpoint(): string {
|
||||
@@ -54,7 +54,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
@@ -302,6 +302,7 @@ export class WebClientServer {
|
||||
@@ -322,6 +322,7 @@ export class WebClientServer {
|
||||
|
||||
const workbenchWebConfiguration = {
|
||||
remoteAuthority,
|
||||
@@ -62,15 +62,6 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
||||
_wrapWebWorkerExtHostInIframe,
|
||||
developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() },
|
||||
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
|
||||
@@ -344,7 +345,7 @@ export class WebClientServer {
|
||||
`script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=';`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
|
||||
'child-src \'self\';',
|
||||
`frame-src 'self' https://*.vscode-cdn.net data:;`,
|
||||
- 'worker-src \'self\' data:;',
|
||||
+ 'worker-src \'self\' data: blob:;',
|
||||
'style-src \'self\' \'unsafe-inline\';',
|
||||
'connect-src \'self\' ws: wss: https:;',
|
||||
'font-src \'self\' blob:;',
|
||||
Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index.html
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index.html
|
||||
@@ -79,12 +70,12 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
- content="default-src 'none'; script-src 'sha256-RaCvj6SRgHm+2C3LKzSAamDwa3Bp4u4iQ1Y2Sm+97tE=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
+ content="default-src 'none'; script-src 'sha256-mi72idjvdhsPSBMKFqU82FG/kZVJjKR0TfHLE13gB+w=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
- content="default-src 'none'; script-src 'sha256-7Y08cqii1UgeZbSST9r8UPSownOSMa3/PiKe77avh7I=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
+ content="default-src 'none'; script-src 'sha256-h057XKGlOvyFweE9mchggtfdly92LQuQ9vKfYvNy7us=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport"
|
||||
@@ -325,6 +325,12 @@
|
||||
@@ -334,6 +334,12 @@
|
||||
|
||||
const hostname = location.hostname;
|
||||
|
||||
@@ -101,7 +92,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
|
||||
===================================================================
|
||||
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
|
||||
+++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
|
||||
@@ -324,6 +324,12 @@
|
||||
@@ -333,6 +333,12 @@
|
||||
|
||||
const hostname = location.hostname;
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ import * as util from "../common/util"
|
||||
import { DefaultedArgs } from "./cli"
|
||||
import { disposer } from "./http"
|
||||
import { isNodeJSErrnoException } from "./util"
|
||||
import { EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
|
||||
import { handleUpgrade } from "./wsRouter"
|
||||
|
||||
type ListenOptions = Pick<DefaultedArgs, "socket-mode" | "socket" | "port" | "host">
|
||||
type SocketOptions = { socket: string; "socket-mode"?: string }
|
||||
type ListenOptions = DefaultedArgs | SocketOptions
|
||||
|
||||
export interface App extends Disposable {
|
||||
/** Handles regular HTTP requests. */
|
||||
@@ -20,12 +22,18 @@ export interface App extends Disposable {
|
||||
wsRouter: Express
|
||||
/** The underlying HTTP server. */
|
||||
server: http.Server
|
||||
/** Handles requests to the editor session management API. */
|
||||
editorSessionManagerServer: http.Server
|
||||
}
|
||||
|
||||
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
|
||||
if (socket) {
|
||||
const isSocketOpts = (opts: ListenOptions): opts is SocketOptions => {
|
||||
return !!(opts as SocketOptions).socket || !(opts as DefaultedArgs).host
|
||||
}
|
||||
|
||||
export const listen = async (server: http.Server, opts: ListenOptions) => {
|
||||
if (isSocketOpts(opts)) {
|
||||
try {
|
||||
await fs.unlink(socket)
|
||||
await fs.unlink(opts.socket)
|
||||
} catch (error: any) {
|
||||
handleArgsSocketCatchError(error)
|
||||
}
|
||||
@@ -38,18 +46,20 @@ export const listen = async (server: http.Server, { host, port, socket, "socket-
|
||||
server.on("error", (err) => util.logError(logger, "http server error", err))
|
||||
resolve()
|
||||
}
|
||||
if (socket) {
|
||||
server.listen(socket, onListen)
|
||||
if (isSocketOpts(opts)) {
|
||||
server.listen(opts.socket, onListen)
|
||||
} else {
|
||||
// [] is the correct format when using :: but Node errors with them.
|
||||
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
|
||||
server.listen(opts.port, opts.host.replace(/^\[|\]$/g, ""), onListen)
|
||||
}
|
||||
})
|
||||
|
||||
// NOTE@jsjoeio: we need to chmod after the server is finished
|
||||
// listening. Otherwise, the socket may not have been created yet.
|
||||
if (socket && mode) {
|
||||
await fs.chmod(socket, mode)
|
||||
if (isSocketOpts(opts)) {
|
||||
if (opts["socket-mode"]) {
|
||||
await fs.chmod(opts.socket, opts["socket-mode"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,14 +80,22 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
|
||||
)
|
||||
: http.createServer(router)
|
||||
|
||||
const dispose = disposer(server)
|
||||
const disposeServer = disposer(server)
|
||||
|
||||
await listen(server, args)
|
||||
|
||||
const wsRouter = express()
|
||||
handleUpgrade(wsRouter, server)
|
||||
|
||||
return { router, wsRouter, server, dispose }
|
||||
const editorSessionManager = new EditorSessionManager()
|
||||
const editorSessionManagerServer = await makeEditorSessionManagerServer(args["session-socket"], editorSessionManager)
|
||||
const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer)
|
||||
|
||||
const dispose = async () => {
|
||||
await Promise.all([disposeServer(), disposeEditorSessionManagerServer()])
|
||||
}
|
||||
|
||||
return { router, wsRouter, server, dispose, editorSessionManagerServer }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
130
src/node/cli.ts
130
src/node/cli.ts
@@ -3,17 +3,8 @@ import { promises as fs } from "fs"
|
||||
import { load } from "js-yaml"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import {
|
||||
canConnect,
|
||||
generateCertificate,
|
||||
generatePassword,
|
||||
humanPath,
|
||||
paths,
|
||||
isNodeJSErrnoException,
|
||||
splitOnFirstEquals,
|
||||
} from "./util"
|
||||
|
||||
const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
|
||||
import { generateCertificate, generatePassword, humanPath, paths, splitOnFirstEquals } from "./util"
|
||||
import { EditorSessionManagerClient } from "./vscodeSocket"
|
||||
|
||||
export enum Feature {
|
||||
// No current experimental features!
|
||||
@@ -60,6 +51,8 @@ export interface UserProvidedCodeArgs {
|
||||
"disable-file-downloads"?: boolean
|
||||
"disable-workspace-trust"?: boolean
|
||||
"disable-getting-started-override"?: boolean
|
||||
"disable-proxy"?: boolean
|
||||
"session-socket"?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +80,7 @@ export interface UserProvidedArgs extends UserProvidedCodeArgs {
|
||||
"bind-addr"?: string
|
||||
socket?: string
|
||||
"socket-mode"?: string
|
||||
"trusted-origins"?: string[]
|
||||
version?: boolean
|
||||
"proxy-domain"?: string[]
|
||||
"reuse-window"?: boolean
|
||||
@@ -169,6 +163,9 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
||||
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
|
||||
"then notifies you once every week that a new release is available.",
|
||||
},
|
||||
"session-socket": {
|
||||
type: "string",
|
||||
},
|
||||
"disable-file-downloads": {
|
||||
type: "boolean",
|
||||
description:
|
||||
@@ -182,6 +179,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
||||
type: "boolean",
|
||||
description: "Disable the coder/coder override in the Help: Getting Started page.",
|
||||
},
|
||||
"disable-proxy": {
|
||||
type: "boolean",
|
||||
description: "Disable domain and path proxy routes.",
|
||||
},
|
||||
// --enable can be used to enable experimental features. These features
|
||||
// provide no guarantees.
|
||||
enable: { type: "string[]" },
|
||||
@@ -213,6 +214,11 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
||||
|
||||
socket: { type: "string", path: true, description: "Path to a socket (bind-addr will be ignored)." },
|
||||
"socket-mode": { type: "string", description: "File mode of the socket." },
|
||||
"trusted-origins": {
|
||||
type: "string[]",
|
||||
description:
|
||||
"Disables authenticate origin check for trusted origin. Useful if not able to access reverse proxy configuration.",
|
||||
},
|
||||
version: { type: "boolean", short: "v", description: "Display version information." },
|
||||
_: { type: "string[]" },
|
||||
|
||||
@@ -433,19 +439,23 @@ export const parse = (
|
||||
throw new Error("--cert-key is missing")
|
||||
}
|
||||
|
||||
logger.debug(() => [
|
||||
`parsed ${opts?.configFile ? "config" : "command line"}`,
|
||||
field("args", {
|
||||
...args,
|
||||
password: args.password ? "<redacted>" : undefined,
|
||||
"hashed-password": args["hashed-password"] ? "<redacted>" : undefined,
|
||||
"github-auth": args["github-auth"] ? "<redacted>" : undefined,
|
||||
}),
|
||||
])
|
||||
logger.debug(() => [`parsed ${opts?.configFile ? "config" : "command line"}`, field("args", redactArgs(args))])
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact sensitive information from arguments for logging.
|
||||
*/
|
||||
export const redactArgs = (args: UserProvidedArgs): UserProvidedArgs => {
|
||||
return {
|
||||
...args,
|
||||
password: args.password ? "<redacted>" : undefined,
|
||||
"hashed-password": args["hashed-password"] ? "<redacted>" : undefined,
|
||||
"github-auth": args["github-auth"] ? "<redacted>" : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User-provided arguments with defaults. The distinction between user-provided
|
||||
* args and defaulted args exists so we can tell the difference between end
|
||||
@@ -464,6 +474,7 @@ export interface DefaultedArgs extends ConfigArgs {
|
||||
usingEnvHashedPassword: boolean
|
||||
"extensions-dir": string
|
||||
"user-data-dir": string
|
||||
"session-socket": string
|
||||
/* Positional arguments. */
|
||||
_: string[]
|
||||
}
|
||||
@@ -484,6 +495,11 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions")
|
||||
}
|
||||
|
||||
if (!args["session-socket"]) {
|
||||
args["session-socket"] = path.join(args["user-data-dir"], "code-server-ipc.sock")
|
||||
}
|
||||
process.env.CODE_SERVER_SESSION_SOCKET = args["session-socket"]
|
||||
|
||||
// --verbose takes priority over --log and --log takes priority over the
|
||||
// environment variable.
|
||||
if (args.verbose) {
|
||||
@@ -553,6 +569,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||
args["disable-getting-started-override"] = true
|
||||
}
|
||||
|
||||
if (process.env.CS_DISABLE_PROXY?.match(/^(1|true)$/)) {
|
||||
args["disable-proxy"] = true
|
||||
}
|
||||
|
||||
const usingEnvHashedPassword = !!process.env.HASHED_PASSWORD
|
||||
if (process.env.HASHED_PASSWORD) {
|
||||
args["hashed-password"] = process.env.HASHED_PASSWORD
|
||||
@@ -570,12 +590,25 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||
|
||||
// Filter duplicate proxy domains and remove any leading `*.`.
|
||||
const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, "")))
|
||||
args["proxy-domain"] = Array.from(proxyDomains)
|
||||
const finalProxies = []
|
||||
|
||||
if (typeof args._ === "undefined") {
|
||||
args._ = []
|
||||
for (const proxyDomain of proxyDomains) {
|
||||
if (!proxyDomain.includes("{{port}}")) {
|
||||
finalProxies.push("{{port}}." + proxyDomain)
|
||||
} else {
|
||||
finalProxies.push(proxyDomain)
|
||||
}
|
||||
}
|
||||
|
||||
// all proxies are of format anyprefix-{{port}}-anysuffix.{{host}}, where {{host}} is optional
|
||||
// e.g. code-8080.domain.tld would match for code-{{port}}.domain.tld and code-{{port}}.{{host}}
|
||||
if (finalProxies.length > 0 && !process.env.VSCODE_PROXY_URI) {
|
||||
process.env.VSCODE_PROXY_URI = `//${finalProxies[0]}`
|
||||
}
|
||||
args["proxy-domain"] = finalProxies
|
||||
|
||||
args._ = getResolvedPathsFromArgs(args)
|
||||
|
||||
return {
|
||||
...args,
|
||||
usingEnvPassword,
|
||||
@@ -583,6 +616,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
||||
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
|
||||
}
|
||||
|
||||
export function getResolvedPathsFromArgs(args: UserProvidedArgs): string[] {
|
||||
return (args._ ?? []).map((p) => path.resolve(p))
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return the default config file.
|
||||
*
|
||||
@@ -722,47 +759,38 @@ function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
|
||||
return addr
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the socketPath based on path passed in.
|
||||
*
|
||||
* The one usually passed in is the DEFAULT_SOCKET_PATH.
|
||||
*
|
||||
* If it can't read the path, it throws an error and returns undefined.
|
||||
*/
|
||||
export async function readSocketPath(path: string): Promise<string | undefined> {
|
||||
try {
|
||||
return await fs.readFile(path, "utf8")
|
||||
} catch (error) {
|
||||
// If it doesn't exist, we don't care.
|
||||
// But if it fails for some reason, we should throw.
|
||||
// We want to surface that to the user.
|
||||
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if it looks like the user is trying to open a file or folder in an
|
||||
* existing instance. The arguments here should be the arguments the user
|
||||
* explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
|
||||
*/
|
||||
export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Promise<string | undefined> => {
|
||||
export const shouldOpenInExistingInstance = async (
|
||||
args: UserProvidedArgs,
|
||||
sessionSocket: string,
|
||||
): Promise<string | undefined> => {
|
||||
// Always use the existing instance if we're running from VS Code's terminal.
|
||||
if (process.env.VSCODE_IPC_HOOK_CLI) {
|
||||
logger.debug("Found VSCODE_IPC_HOOK_CLI")
|
||||
return process.env.VSCODE_IPC_HOOK_CLI
|
||||
}
|
||||
|
||||
const paths = getResolvedPathsFromArgs(args)
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
|
||||
// If these flags are set then assume the user is trying to open in an
|
||||
// existing instance since these flags have no effect otherwise.
|
||||
// existing instance since these flags have no effect otherwise. That means
|
||||
// if there is no existing instance we should error rather than falling back
|
||||
// to spawning code-server normally.
|
||||
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
|
||||
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
|
||||
}, 0)
|
||||
if (openInFlagCount > 0) {
|
||||
logger.debug("Found --reuse-window or --new-window")
|
||||
return readSocketPath(DEFAULT_SOCKET_PATH)
|
||||
const socketPath = await client.getConnectedSocketPath(paths[0])
|
||||
if (!socketPath) {
|
||||
throw new Error(`No opened code-server instances found to handle ${paths[0]}`)
|
||||
}
|
||||
return socketPath
|
||||
}
|
||||
|
||||
// It's possible the user is trying to spawn another instance of code-server.
|
||||
@@ -770,9 +798,13 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
|
||||
// code-server is invoked exactly like this: `code-server my-file`).
|
||||
// 2. That a file or directory was passed.
|
||||
// 3. That the socket is active.
|
||||
// 4. That an instance exists to handle the path (implied by #3).
|
||||
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
|
||||
const socketPath = await readSocketPath(DEFAULT_SOCKET_PATH)
|
||||
if (socketPath && (await canConnect(socketPath))) {
|
||||
if (!(await client.canConnect())) {
|
||||
return undefined
|
||||
}
|
||||
const socketPath = await client.getConnectedSocketPath(paths[0])
|
||||
if (socketPath) {
|
||||
logger.debug("Found existing code-server socket")
|
||||
return socketPath
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ async function entry(): Promise<void> {
|
||||
return runCodeCli(args)
|
||||
}
|
||||
|
||||
const socketPath = await shouldOpenInExistingInstance(cliArgs)
|
||||
const socketPath = await shouldOpenInExistingInstance(cliArgs, args["session-socket"])
|
||||
if (socketPath) {
|
||||
logger.debug("Trying to open in existing instance")
|
||||
return openInExistingInstance(args, socketPath)
|
||||
|
||||
@@ -75,6 +75,25 @@ export const replaceTemplates = <T extends object>(
|
||||
.replace("{{OPTIONS}}", () => escapeJSON(serverOptions))
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error if proxy is not enabled. Call `next` if provided.
|
||||
*/
|
||||
export const ensureProxyEnabled = (req: express.Request, _?: express.Response, next?: express.NextFunction): void => {
|
||||
if (!proxyEnabled(req)) {
|
||||
throw new HttpError("Forbidden", HttpCode.Forbidden)
|
||||
}
|
||||
if (next) {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if proxy is enabled.
|
||||
*/
|
||||
export const proxyEnabled = (req: express.Request): boolean => {
|
||||
return !req.args["disable-proxy"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error if not authorized. Call `next` if provided.
|
||||
*/
|
||||
@@ -323,35 +342,62 @@ function getFirstHeader(req: http.IncomingMessage, headerName: string): string |
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error if origin checks fail. Call `next` if provided.
|
||||
* Throw a forbidden error if origin checks fail. Call `next` if provided.
|
||||
*/
|
||||
export function ensureOrigin(req: express.Request, _?: express.Response, next?: express.NextFunction): void {
|
||||
if (!authenticateOrigin(req)) {
|
||||
try {
|
||||
authenticateOrigin(req)
|
||||
if (next) {
|
||||
next()
|
||||
}
|
||||
} catch (error) {
|
||||
logger.debug(`${error instanceof Error ? error.message : error}; blocking request to ${req.originalUrl}`)
|
||||
throw new HttpError("Forbidden", HttpCode.Forbidden)
|
||||
}
|
||||
if (next) {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the request origin against the host.
|
||||
* Authenticate the request origin against the host. Throw if invalid.
|
||||
*/
|
||||
export function authenticateOrigin(req: express.Request): boolean {
|
||||
export function authenticateOrigin(req: express.Request): void {
|
||||
// A missing origin probably means the source is non-browser. Not sure we
|
||||
// have a use case for this but let it through.
|
||||
const originRaw = getFirstHeader(req, "origin")
|
||||
if (!originRaw) {
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
let origin: string
|
||||
try {
|
||||
origin = new URL(originRaw).host.trim().toLowerCase()
|
||||
} catch (error) {
|
||||
return false // Malformed URL.
|
||||
throw new Error(`unable to parse malformed origin "${originRaw}"`)
|
||||
}
|
||||
|
||||
const trustedOrigins = req.args["trusted-origins"] || []
|
||||
if (trustedOrigins.includes(origin) || trustedOrigins.includes("*")) {
|
||||
return
|
||||
}
|
||||
|
||||
const host = getHost(req)
|
||||
if (typeof host === "undefined") {
|
||||
// A missing host likely means the reverse proxy has not been configured to
|
||||
// forward the host which means we cannot perform the check. Emit an error
|
||||
// so an admin can fix the issue.
|
||||
logger.error("No host headers found")
|
||||
logger.error("Are you behind a reverse proxy that does not forward the host?")
|
||||
throw new Error("no host headers found")
|
||||
}
|
||||
|
||||
if (host !== origin) {
|
||||
throw new Error(`host "${host}" does not match origin "${origin}"`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the host from headers. It will be trimmed and lowercased.
|
||||
*/
|
||||
export function getHost(req: express.Request): string | undefined {
|
||||
// Honor Forwarded if present.
|
||||
const forwardedRaw = getFirstHeader(req, "forwarded")
|
||||
if (forwardedRaw) {
|
||||
@@ -359,25 +405,21 @@ export function authenticateOrigin(req: express.Request): boolean {
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
const [key, value] = splitOnFirstEquals(parts[i])
|
||||
if (key.trim().toLowerCase() === "host" && value) {
|
||||
return origin === value.trim().toLowerCase()
|
||||
return value.trim().toLowerCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Honor X-Forwarded-Host if present.
|
||||
// Honor X-Forwarded-Host if present. Some reverse proxies will set multiple
|
||||
// comma-separated hosts.
|
||||
const xHost = getFirstHeader(req, "x-forwarded-host")
|
||||
if (xHost) {
|
||||
return origin === xHost.trim().toLowerCase()
|
||||
const firstXHost = xHost.split(",")[0]
|
||||
if (firstXHost) {
|
||||
return firstXHost.trim().toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
// A missing host likely means the reverse proxy has not been configured to
|
||||
// forward the host which means we cannot perform the check. Emit a warning
|
||||
// so an admin can fix the issue.
|
||||
const host = getFirstHeader(req, "host")
|
||||
if (!host) {
|
||||
logger.warn(`no host headers found; blocking request to ${req.originalUrl}`)
|
||||
return false
|
||||
}
|
||||
|
||||
return origin === host.trim().toLowerCase()
|
||||
return host ? host.trim().toLowerCase() : undefined
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import i18next, { init } from "i18next"
|
||||
import * as en from "./locales/en.json"
|
||||
import * as zhCn from "./locales/zh-cn.json"
|
||||
|
||||
import * as th from "./locales/th.json"
|
||||
init({
|
||||
lng: "en",
|
||||
fallbackLng: "en", // language to use if translations in user language are not available.
|
||||
@@ -15,6 +15,9 @@ init({
|
||||
"zh-cn": {
|
||||
translation: zhCn,
|
||||
},
|
||||
th: {
|
||||
translation: th,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
13
src/node/i18n/locales/th.json
Normal file
13
src/node/i18n/locales/th.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"LOGIN_TITLE": "เข้าสู่ระบบ {{app}}",
|
||||
"LOGIN_BELOW": "กรุณาเข้าสู่ระบบด้านล่าง",
|
||||
"WELCOME": "ยินดีต้อนรับสู่ {{app}}",
|
||||
"LOGIN_PASSWORD": "ตรวจสอบไฟล์กำหนดค่าที่ {{configFile}} เพื่อดูรหัสผ่าน",
|
||||
"LOGIN_USING_ENV_PASSWORD": "รหัสผ่านถูกกำหนดเป็น $PASSWORD",
|
||||
"LOGIN_USING_HASHED_PASSWORD": "รรหัสผ่านถูกกำหนดเป็น $HASHED_PASSWORD",
|
||||
"SUBMIT": "ส่ง",
|
||||
"PASSWORD_PLACEHOLDER": "รหัสผ่าน",
|
||||
"LOGIN_RATE_LIMIT": "ถึงขีดจำกัดอัตราการเข้าสู่ระบบ!",
|
||||
"MISS_PASSWORD": "รหัสผ่านหายไป",
|
||||
"INCORRECT_PASSWORD": "รหัสผ่านไม่ถูกต้อง"
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { field, logger } from "@coder/logger"
|
||||
import http from "http"
|
||||
import * as os from "os"
|
||||
import path from "path"
|
||||
import { Disposable } from "../common/emitter"
|
||||
import { plural } from "../common/util"
|
||||
import { createApp, ensureAddress } from "./app"
|
||||
@@ -70,9 +69,8 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
|
||||
forceNewWindow: args["new-window"],
|
||||
gotoLineMode: true,
|
||||
}
|
||||
const paths = args._ || []
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const fp = path.resolve(paths[i])
|
||||
for (let i = 0; i < args._.length; i++) {
|
||||
const fp = args._[i]
|
||||
if (await isDirectory(fp)) {
|
||||
pipeArgs.folderURIs.push(fp)
|
||||
} else {
|
||||
@@ -127,7 +125,6 @@ export const runCodeServer = async (
|
||||
|
||||
logger.info(`Using config file ${humanPath(os.homedir(), args.config)}`)
|
||||
logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
|
||||
|
||||
if (args.auth === AuthType.Password) {
|
||||
logger.info(" - Authentication is enabled")
|
||||
if (args.usingEnvPassword) {
|
||||
@@ -147,9 +144,19 @@ export const runCodeServer = async (
|
||||
logger.info(" - Not serving HTTPS")
|
||||
}
|
||||
|
||||
if (args["proxy-domain"].length > 0) {
|
||||
if (args["disable-proxy"]) {
|
||||
logger.info(" - Proxy disabled")
|
||||
} else if (args["proxy-domain"].length > 0) {
|
||||
logger.info(` - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`)
|
||||
args["proxy-domain"].forEach((domain) => logger.info(` - *.${domain}`))
|
||||
args["proxy-domain"].forEach((domain) => logger.info(` - ${domain}`))
|
||||
}
|
||||
if (process.env.VSCODE_PROXY_URI) {
|
||||
logger.info(`Using proxy URI in PORTS tab: ${process.env.VSCODE_PROXY_URI}`)
|
||||
}
|
||||
|
||||
const sessionServerAddress = app.editorSessionManagerServer.address()
|
||||
if (sessionServerAddress) {
|
||||
logger.info(`Session server listening on ${sessionServerAddress.toString()}`)
|
||||
}
|
||||
|
||||
if (args.enable && args.enable.length > 0) {
|
||||
|
||||
@@ -3,9 +3,19 @@ import { HttpCode } from "../common/http"
|
||||
|
||||
export const proxy = proxyServer.createProxyServer({})
|
||||
|
||||
// The error handler catches when the proxy fails to connect (for example when
|
||||
// there is nothing running on the target port).
|
||||
proxy.on("error", (error, _, res) => {
|
||||
res.writeHead(HttpCode.ServerError)
|
||||
res.end(error.message)
|
||||
// This could be for either a web socket or a regular request. Despite what
|
||||
// the types say, writeHead() will not exist on web socket requests (nor will
|
||||
// status() from Express). But writing out the code manually does not work
|
||||
// for regular requests thus the branching behavior.
|
||||
if (typeof res.writeHead !== "undefined") {
|
||||
res.writeHead(HttpCode.ServerError)
|
||||
res.end(error.message)
|
||||
} else {
|
||||
res.end(`HTTP/1.1 ${HttpCode.ServerError} ${error.message}\r\n\r\n`)
|
||||
}
|
||||
})
|
||||
|
||||
// Intercept the response to rewrite absolute redirects against the base path.
|
||||
|
||||
@@ -1,34 +1,56 @@
|
||||
import { Request, Router } from "express"
|
||||
import { HttpCode, HttpError } from "../../common/http"
|
||||
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
|
||||
import { getHost, ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
|
||||
import { proxy } from "../proxy"
|
||||
import { Router as WsRouter } from "../wsRouter"
|
||||
|
||||
export const router = Router()
|
||||
|
||||
const proxyDomainToRegex = (matchString: string): RegExp => {
|
||||
const escapedMatchString = matchString.replace(/[.*+?^$()|[\]\\]/g, "\\$&")
|
||||
|
||||
// Replace {{port}} with a regex group to capture the port
|
||||
// Replace {{host}} with .+ to allow any host match (so rely on DNS record here)
|
||||
let regexString = escapedMatchString.replace("{{port}}", "(\\d+)")
|
||||
regexString = regexString.replace("{{host}}", ".+")
|
||||
|
||||
regexString = regexString.replace(/[{}]/g, "\\$&") //replace any '{}' that might be left
|
||||
|
||||
return new RegExp("^" + regexString + "$")
|
||||
}
|
||||
|
||||
let proxyRegexes: RegExp[] = []
|
||||
const proxyDomainsToRegex = (proxyDomains: string[]): RegExp[] => {
|
||||
if (proxyDomains.length !== proxyRegexes.length) {
|
||||
proxyRegexes = proxyDomains.map(proxyDomainToRegex)
|
||||
}
|
||||
return proxyRegexes
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the port if the request should be proxied. Anything that ends in a
|
||||
* proxy domain and has a *single* subdomain should be proxied. Anything else
|
||||
* should return `undefined` and will be handled as normal.
|
||||
* Return the port if the request should be proxied.
|
||||
*
|
||||
* The proxy-domain should be of format anyprefix-{{port}}-anysuffix.{{host}}, where {{host}} is optional
|
||||
* e.g. code-8080.domain.tld would match for code-{{port}}.domain.tld and code-{{port}}.{{host}}.
|
||||
*
|
||||
* For example if `coder.com` is specified `8080.coder.com` will be proxied
|
||||
* but `8080.test.coder.com` and `test.8080.coder.com` will not.
|
||||
*/
|
||||
const maybeProxy = (req: Request): string | undefined => {
|
||||
// Split into parts.
|
||||
const host = req.headers.host || ""
|
||||
const idx = host.indexOf(":")
|
||||
const domain = idx !== -1 ? host.substring(0, idx) : host
|
||||
const parts = domain.split(".")
|
||||
|
||||
// There must be an exact match.
|
||||
const port = parts.shift()
|
||||
const proxyDomain = parts.join(".")
|
||||
if (!port || !req.args["proxy-domain"].includes(proxyDomain)) {
|
||||
const reqDomain = getHost(req)
|
||||
if (reqDomain === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return port
|
||||
const regexs = proxyDomainsToRegex(req.args["proxy-domain"])
|
||||
|
||||
for (const regex of regexs) {
|
||||
const match = reqDomain.match(regex)
|
||||
|
||||
if (match) {
|
||||
return match[1] // match[1] contains the port
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
router.all("*", async (req, res, next) => {
|
||||
@@ -37,6 +59,8 @@ router.all("*", async (req, res, next) => {
|
||||
return next()
|
||||
}
|
||||
|
||||
ensureProxyEnabled(req)
|
||||
|
||||
// Must be authenticated to use the proxy.
|
||||
const isAuthenticated = await authenticated(req)
|
||||
if (!isAuthenticated) {
|
||||
@@ -78,6 +102,8 @@ wsRouter.ws("*", async (req, _, next) => {
|
||||
if (!port) {
|
||||
return next()
|
||||
}
|
||||
|
||||
ensureProxyEnabled(req)
|
||||
ensureOrigin(req)
|
||||
await ensureAuthenticated(req)
|
||||
proxy.ws(req, req.ws, req.head, {
|
||||
|
||||
@@ -62,12 +62,16 @@ export const errorHandler: express.ErrorRequestHandler = async (err, req, res, n
|
||||
}
|
||||
|
||||
export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
||||
logger.error(`${err.message} ${err.stack}`)
|
||||
let statusCode = 500
|
||||
if (errorHasStatusCode(err)) {
|
||||
statusCode = err.statusCode
|
||||
} else if (errorHasCode(err) && notFoundCodes.includes(err.code)) {
|
||||
statusCode = HttpCode.NotFound
|
||||
}
|
||||
if (statusCode >= 500) {
|
||||
logger.error(`${err.message} ${err.stack}`)
|
||||
} else {
|
||||
logger.debug(`${err.message} ${err.stack}`)
|
||||
}
|
||||
;(req as WebsocketRequest).ws.end(`HTTP/1.1 ${statusCode} ${err.message}\r\n\r\n`)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as path from "path"
|
||||
import * as qs from "qs"
|
||||
import * as pluginapi from "../../../typings/pluginapi"
|
||||
import { HttpCode, HttpError } from "../../common/http"
|
||||
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
|
||||
import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
|
||||
import { proxy as _proxy } from "../proxy"
|
||||
|
||||
const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
|
||||
@@ -11,7 +11,7 @@ const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
|
||||
return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
|
||||
}
|
||||
const query = qs.stringify(req.query)
|
||||
return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}`
|
||||
return encodeURI(`http://0.0.0.0:${req.params.port}${req.params[0] || ""}${query ? `?${query}` : ""}`)
|
||||
}
|
||||
|
||||
export async function proxy(
|
||||
@@ -21,6 +21,8 @@ export async function proxy(
|
||||
passthroughPath?: boolean
|
||||
},
|
||||
): Promise<void> {
|
||||
ensureProxyEnabled(req)
|
||||
|
||||
if (!(await authenticated(req))) {
|
||||
// If visiting the root (/:port only) redirect to the login page.
|
||||
if (!req.params[0] || req.params[0] === "/") {
|
||||
@@ -50,6 +52,7 @@ export async function wsProxy(
|
||||
passthroughPath?: boolean
|
||||
},
|
||||
): Promise<void> {
|
||||
ensureProxyEnabled(req)
|
||||
ensureOrigin(req)
|
||||
await ensureAuthenticated(req)
|
||||
_proxy.ws(req, req.ws, req.head, {
|
||||
|
||||
@@ -40,6 +40,7 @@ export class CodeServerRouteWrapper {
|
||||
//#region Route Handlers
|
||||
|
||||
private manifest: express.Handler = async (req, res, next) => {
|
||||
const appName = req.args["app-name"] || "code-server"
|
||||
res.writeHead(200, { "Content-Type": "application/manifest+json" })
|
||||
|
||||
return res.end(
|
||||
@@ -47,8 +48,8 @@ export class CodeServerRouteWrapper {
|
||||
req,
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "code-server",
|
||||
short_name: "code-server",
|
||||
name: appName,
|
||||
short_name: appName,
|
||||
start_url: ".",
|
||||
display: "fullscreen",
|
||||
description: "Run Code on a remote server.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { field, logger } from "@coder/logger"
|
||||
import * as http from "http"
|
||||
import * as https from "https"
|
||||
import ProxyAgent from "proxy-agent"
|
||||
import { ProxyAgent } from "proxy-agent"
|
||||
import * as semver from "semver"
|
||||
import * as url from "url"
|
||||
import { httpProxyUri, version } from "./constants"
|
||||
@@ -104,7 +104,9 @@ export class UpdateProvider {
|
||||
const request = (uri: string): void => {
|
||||
logger.debug("Making request", field("uri", uri))
|
||||
const isHttps = uri.startsWith("https")
|
||||
const agent = httpProxyUri ? new ProxyAgent(httpProxyUri) : undefined
|
||||
const agent = new ProxyAgent({
|
||||
getProxyForUrl: () => httpProxyUri || "",
|
||||
})
|
||||
const httpx = isHttps ? https : http
|
||||
const client = httpx.get(uri, { headers: { "User-Agent": "code-server" }, agent }, (response) => {
|
||||
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 400) {
|
||||
|
||||
206
src/node/vscodeSocket.ts
Normal file
206
src/node/vscodeSocket.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { logger } from "@coder/logger"
|
||||
import express from "express"
|
||||
import * as http from "http"
|
||||
import * as path from "path"
|
||||
import { HttpCode } from "../common/http"
|
||||
import { listen } from "./app"
|
||||
import { canConnect } from "./util"
|
||||
|
||||
export interface EditorSessionEntry {
|
||||
workspace: {
|
||||
id: string
|
||||
folders: {
|
||||
uri: {
|
||||
path: string
|
||||
}
|
||||
}[]
|
||||
}
|
||||
|
||||
socketPath: string
|
||||
}
|
||||
|
||||
interface DeleteSessionRequest {
|
||||
socketPath: string
|
||||
}
|
||||
|
||||
interface AddSessionRequest {
|
||||
entry: EditorSessionEntry
|
||||
}
|
||||
|
||||
interface GetSessionResponse {
|
||||
socketPath?: string
|
||||
}
|
||||
|
||||
export async function makeEditorSessionManagerServer(
|
||||
codeServerSocketPath: string,
|
||||
editorSessionManager: EditorSessionManager,
|
||||
): Promise<http.Server> {
|
||||
const router = express()
|
||||
|
||||
// eslint-disable-next-line import/no-named-as-default-member
|
||||
router.use(express.json())
|
||||
|
||||
router.get("/session", async (req, res) => {
|
||||
const filePath = req.query.filePath as string
|
||||
if (!filePath) {
|
||||
res.status(HttpCode.BadRequest).send("filePath is required")
|
||||
return
|
||||
}
|
||||
try {
|
||||
const socketPath = await editorSessionManager.getConnectedSocketPath(filePath)
|
||||
const response: GetSessionResponse = { socketPath }
|
||||
res.json(response)
|
||||
} catch (error: unknown) {
|
||||
res.status(HttpCode.ServerError).send(error)
|
||||
}
|
||||
})
|
||||
|
||||
router.post("/add-session", async (req, res) => {
|
||||
const request = req.body as AddSessionRequest
|
||||
if (!request.entry) {
|
||||
res.status(400).send("entry is required")
|
||||
}
|
||||
editorSessionManager.addSession(request.entry)
|
||||
res.status(200).send()
|
||||
})
|
||||
|
||||
router.post("/delete-session", async (req, res) => {
|
||||
const request = req.body as DeleteSessionRequest
|
||||
if (!request.socketPath) {
|
||||
res.status(400).send("socketPath is required")
|
||||
}
|
||||
editorSessionManager.deleteSession(request.socketPath)
|
||||
res.status(200).send()
|
||||
})
|
||||
|
||||
const server = http.createServer(router)
|
||||
try {
|
||||
await listen(server, { socket: codeServerSocketPath })
|
||||
} catch (e) {
|
||||
logger.warn(`Could not create socket at ${codeServerSocketPath}`)
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
export class EditorSessionManager {
|
||||
// Map from socket path to EditorSessionEntry.
|
||||
private entries = new Map<string, EditorSessionEntry>()
|
||||
|
||||
addSession(entry: EditorSessionEntry): void {
|
||||
logger.debug(`Adding session to session registry: ${entry.socketPath}`)
|
||||
this.entries.set(entry.socketPath, entry)
|
||||
}
|
||||
|
||||
getCandidatesForFile(filePath: string): EditorSessionEntry[] {
|
||||
const matchCheckResults = new Map<string, boolean>()
|
||||
|
||||
const checkMatch = (entry: EditorSessionEntry): boolean => {
|
||||
if (matchCheckResults.has(entry.socketPath)) {
|
||||
return matchCheckResults.get(entry.socketPath)!
|
||||
}
|
||||
const result = entry.workspace.folders.some((folder) => filePath.startsWith(folder.uri.path + path.sep))
|
||||
matchCheckResults.set(entry.socketPath, result)
|
||||
return result
|
||||
}
|
||||
|
||||
return Array.from(this.entries.values())
|
||||
.reverse() // Most recently registered first.
|
||||
.sort((a, b) => {
|
||||
// Matches first.
|
||||
const aMatch = checkMatch(a)
|
||||
const bMatch = checkMatch(b)
|
||||
if (aMatch === bMatch) {
|
||||
return 0
|
||||
}
|
||||
if (aMatch) {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
}
|
||||
|
||||
deleteSession(socketPath: string): void {
|
||||
logger.debug(`Deleting session from session registry: ${socketPath}`)
|
||||
this.entries.delete(socketPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best socket path that we can connect to.
|
||||
* We also delete any sockets that we can't connect to.
|
||||
*/
|
||||
async getConnectedSocketPath(filePath: string): Promise<string | undefined> {
|
||||
const candidates = this.getCandidatesForFile(filePath)
|
||||
let match: EditorSessionEntry | undefined = undefined
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (await canConnect(candidate.socketPath)) {
|
||||
match = candidate
|
||||
break
|
||||
}
|
||||
this.deleteSession(candidate.socketPath)
|
||||
}
|
||||
|
||||
return match?.socketPath
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorSessionManagerClient {
|
||||
constructor(private codeServerSocketPath: string) {}
|
||||
|
||||
async canConnect() {
|
||||
return canConnect(this.codeServerSocketPath)
|
||||
}
|
||||
|
||||
async getConnectedSocketPath(filePath: string): Promise<string | undefined> {
|
||||
const response = await new Promise<GetSessionResponse>((resolve, reject) => {
|
||||
const opts = {
|
||||
path: "/session?filePath=" + encodeURIComponent(filePath),
|
||||
socketPath: this.codeServerSocketPath,
|
||||
method: "GET",
|
||||
}
|
||||
const req = http.request(opts, (res) => {
|
||||
let rawData = ""
|
||||
res.setEncoding("utf8")
|
||||
res.on("data", (chunk) => {
|
||||
rawData += chunk
|
||||
})
|
||||
res.on("end", () => {
|
||||
try {
|
||||
const obj = JSON.parse(rawData)
|
||||
if (res.statusCode === 200) {
|
||||
resolve(obj)
|
||||
} else {
|
||||
reject(new Error("Unexpected status code: " + res.statusCode))
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
})
|
||||
req.on("error", reject)
|
||||
req.end()
|
||||
})
|
||||
return response.socketPath
|
||||
}
|
||||
|
||||
// Currently only used for tests.
|
||||
async addSession(request: AddSessionRequest): Promise<void> {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const opts = {
|
||||
path: "/add-session",
|
||||
socketPath: this.codeServerSocketPath,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
accept: "application/json",
|
||||
},
|
||||
}
|
||||
const req = http.request(opts, () => {
|
||||
resolve()
|
||||
})
|
||||
req.on("error", reject)
|
||||
req.write(JSON.stringify(request))
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import * as cp from "child_process"
|
||||
import * as path from "path"
|
||||
import * as rfs from "rotating-file-stream"
|
||||
import { Emitter } from "../common/emitter"
|
||||
import { DefaultedArgs } from "./cli"
|
||||
import { DefaultedArgs, redactArgs } from "./cli"
|
||||
import { paths } from "./util"
|
||||
|
||||
const timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
|
||||
@@ -44,10 +44,11 @@ export function onMessage<M, T extends M>(
|
||||
}
|
||||
|
||||
const onMessage = (message: M) => {
|
||||
;(customLogger || logger).trace("got message", field("message", message))
|
||||
if (fn(message)) {
|
||||
cleanup()
|
||||
resolve(message)
|
||||
} else {
|
||||
;(customLogger || logger).debug("got unhandled message", field("message", message))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +173,7 @@ export class ChildProcess extends Process {
|
||||
* Initiate the handshake and wait for a response from the parent.
|
||||
*/
|
||||
public async handshake(): Promise<DefaultedArgs> {
|
||||
this.logger.debug("initiating handshake")
|
||||
this.send({ type: "handshake" })
|
||||
const message = await onMessage<ParentMessage, ParentHandshakeMessage>(
|
||||
process,
|
||||
@@ -180,6 +182,13 @@ export class ChildProcess extends Process {
|
||||
},
|
||||
this.logger,
|
||||
)
|
||||
this.logger.debug(
|
||||
"got message",
|
||||
field("message", {
|
||||
type: message.type,
|
||||
args: redactArgs(message.args),
|
||||
}),
|
||||
)
|
||||
return message.args
|
||||
}
|
||||
|
||||
@@ -280,6 +289,10 @@ export class ParentProcess extends Process {
|
||||
}
|
||||
|
||||
public start(args: DefaultedArgs): Promise<void> {
|
||||
// Our logger was created before we parsed CLI arguments so update the level
|
||||
// in case it has changed.
|
||||
this.logger.level = logger.level
|
||||
|
||||
// Store for relaunches.
|
||||
this.args = args
|
||||
if (!this.started) {
|
||||
@@ -306,7 +319,7 @@ export class ParentProcess extends Process {
|
||||
})
|
||||
}
|
||||
|
||||
this.logger.debug(`spawned inner process ${child.pid}`)
|
||||
this.logger.debug(`spawned child process ${child.pid}`)
|
||||
|
||||
await this.handshake(child)
|
||||
|
||||
@@ -334,13 +347,14 @@ export class ParentProcess extends Process {
|
||||
if (!this.args) {
|
||||
throw new Error("started without args")
|
||||
}
|
||||
await onMessage<ChildMessage, ChildHandshakeMessage>(
|
||||
const message = await onMessage<ChildMessage, ChildHandshakeMessage>(
|
||||
child,
|
||||
(message): message is ChildHandshakeMessage => {
|
||||
return message.type === "handshake"
|
||||
},
|
||||
this.logger,
|
||||
)
|
||||
this.logger.debug("got message", field("message", message))
|
||||
this.send(child, { type: "handshake", args: this.args })
|
||||
}
|
||||
|
||||
|
||||
@@ -117,40 +117,26 @@ export class CodeServer {
|
||||
* directories.
|
||||
*/
|
||||
private async spawn(): Promise<CodeServerProcess> {
|
||||
// This will be used both as the workspace and data directory to ensure
|
||||
// instances don't bleed into each other.
|
||||
const dir = await this.createWorkspace()
|
||||
|
||||
const args = await this.argsWithDefaults([
|
||||
"--auth",
|
||||
"none",
|
||||
// The workspace to open.
|
||||
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
|
||||
...this.args,
|
||||
// Using port zero will spawn on a random port.
|
||||
"--bind-addr",
|
||||
"127.0.0.1:0",
|
||||
])
|
||||
return new Promise((resolve, reject) => {
|
||||
const args = [
|
||||
this.entry,
|
||||
"--extensions-dir",
|
||||
path.join(dir, "extensions"),
|
||||
"--auth",
|
||||
"none",
|
||||
// The workspace to open.
|
||||
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
|
||||
...this.args,
|
||||
// Using port zero will spawn on a random port.
|
||||
"--bind-addr",
|
||||
"127.0.0.1:0",
|
||||
// Setting the XDG variables would be easier and more thorough but the
|
||||
// modules we import ignores those variables for non-Linux operating
|
||||
// systems so use these flags instead.
|
||||
"--config",
|
||||
path.join(dir, "config.yaml"),
|
||||
"--user-data-dir",
|
||||
dir,
|
||||
]
|
||||
this.logger.debug("spawning `node " + args.join(" ") + "`")
|
||||
const proc = cp.spawn("node", args, {
|
||||
cwd: path.join(__dirname, "../../.."),
|
||||
env: {
|
||||
...process.env,
|
||||
...this.env,
|
||||
// Set to empty string to prevent code-server from
|
||||
// using the existing instance when running the e2e tests
|
||||
// from an integrated terminal.
|
||||
// Prevent code-server from using the existing instance when running
|
||||
// the e2e tests from an integrated terminal.
|
||||
VSCODE_IPC_HOOK_CLI: "",
|
||||
PASSWORD,
|
||||
},
|
||||
@@ -173,11 +159,15 @@ export class CodeServer {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
// Tracks when the HTTP and session servers are ready.
|
||||
let httpAddress: string | undefined
|
||||
let sessionAddress: string | undefined
|
||||
|
||||
let resolved = false
|
||||
proc.stdout.setEncoding("utf8")
|
||||
onLine(proc, (line) => {
|
||||
// As long as we are actively getting input reset the timer. If we stop
|
||||
// getting input and still have not found the address the timer will
|
||||
// getting input and still have not found the addresses the timer will
|
||||
// reject.
|
||||
timer.reset()
|
||||
|
||||
@@ -186,20 +176,69 @@ export class CodeServer {
|
||||
if (resolved) {
|
||||
return
|
||||
}
|
||||
const match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
|
||||
|
||||
let match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
|
||||
if (match) {
|
||||
// Cookies don't seem to work on IP address so swap to localhost.
|
||||
// Cookies don't seem to work on IP addresses so swap to localhost.
|
||||
// TODO: Investigate whether this is a bug with code-server.
|
||||
const address = match[1].replace("127.0.0.1", "localhost")
|
||||
this.logger.debug(`spawned on ${address}`)
|
||||
httpAddress = match[1].replace("127.0.0.1", "localhost")
|
||||
}
|
||||
|
||||
match = line.trim().match(/Session server listening on (.+)$/)
|
||||
if (match) {
|
||||
sessionAddress = match[1]
|
||||
}
|
||||
|
||||
if (typeof httpAddress !== "undefined" && typeof sessionAddress !== "undefined") {
|
||||
resolved = true
|
||||
timer.dispose()
|
||||
resolve({ process: proc, address })
|
||||
this.logger.debug(`code-server is ready: ${httpAddress} ${sessionAddress}`)
|
||||
resolve({ process: proc, address: httpAddress })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a short-lived command.
|
||||
*/
|
||||
async run(args: string[]): Promise<void> {
|
||||
args = await this.argsWithDefaults(args)
|
||||
this.logger.debug("executing `node " + args.join(" ") + "`")
|
||||
await util.promisify(cp.exec)("node " + args.join(" "), {
|
||||
cwd: path.join(__dirname, "../../.."),
|
||||
env: {
|
||||
...process.env,
|
||||
...this.env,
|
||||
// Prevent code-server from using the existing instance when running
|
||||
// the e2e tests from an integrated terminal.
|
||||
VSCODE_IPC_HOOK_CLI: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine arguments with defaults.
|
||||
*/
|
||||
private async argsWithDefaults(args: string[]): Promise<string[]> {
|
||||
// This will be used both as the workspace and data directory to ensure
|
||||
// instances don't bleed into each other.
|
||||
const dir = await this.workspaceDir
|
||||
return [
|
||||
this.entry,
|
||||
"--extensions-dir",
|
||||
path.join(dir, "extensions"),
|
||||
...args,
|
||||
// Setting the XDG variables would be easier and more thorough but the
|
||||
// modules we import ignores those variables for non-Linux operating
|
||||
// systems so use these flags instead.
|
||||
"--config",
|
||||
path.join(dir, "config.yaml"),
|
||||
"--user-data-dir",
|
||||
dir,
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the code-server process.
|
||||
*/
|
||||
@@ -364,6 +403,13 @@ export class CodeServerPage {
|
||||
await this.waitForTab(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a file through an external command.
|
||||
*/
|
||||
async openFileExternally(file: string) {
|
||||
await this.codeServer.run(["--reuse-window", file])
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a tab to open for the specified file.
|
||||
*/
|
||||
|
||||
@@ -35,13 +35,19 @@ describe("Integrated Terminal", ["--disable-workspace-trust"], {}, () => {
|
||||
const tmpFolderPath = await tmpdir(testName)
|
||||
const tmpFile = path.join(tmpFolderPath, "test-file")
|
||||
await fs.writeFile(tmpFile, "test")
|
||||
const fileName = path.basename(tmpFile)
|
||||
|
||||
await codeServerPage.focusTerminal()
|
||||
|
||||
await codeServerPage.page.keyboard.type(`code-server ${tmpFile}`)
|
||||
await codeServerPage.page.keyboard.press("Enter")
|
||||
|
||||
await codeServerPage.waitForTab(fileName)
|
||||
await codeServerPage.waitForTab(path.basename(tmpFile))
|
||||
|
||||
const externalTmpFile = path.join(tmpFolderPath, "test-external-file")
|
||||
await fs.writeFile(externalTmpFile, "foobar")
|
||||
|
||||
await codeServerPage.openFileExternally(externalTmpFile)
|
||||
|
||||
await codeServerPage.waitForTab(path.basename(externalTmpFile))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("--install-extension", () => {
|
||||
setupFlags = ["--extensions-dir", tempDir]
|
||||
})
|
||||
it("should use EXTENSIONS_GALLERY when set", async () => {
|
||||
const extName = `author.extension-1.0.0`
|
||||
const extName = "author.extension"
|
||||
const { stderr } = await runCodeServerCommand([...setupFlags, "--install-extension", extName], {
|
||||
EXTENSIONS_GALLERY: "{}",
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"jest": "^27.3.1",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jsdom": "^16.4.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"playwright": "^1.16.3",
|
||||
"supertest": "^6.1.6",
|
||||
"ts-jest": "^27.0.7",
|
||||
|
||||
@@ -17,7 +17,7 @@ describe("createApp", () => {
|
||||
beforeAll(async () => {
|
||||
mockLogger()
|
||||
|
||||
const testName = "unlink-socket"
|
||||
const testName = "app"
|
||||
await clean(testName)
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
tmpFilePath = path.join(tmpDirPath, "unlink-socket-file")
|
||||
@@ -103,7 +103,7 @@ describe("createApp", () => {
|
||||
|
||||
const app = await createApp(defaultArgs)
|
||||
|
||||
expect(unlinkSpy).toHaveBeenCalledTimes(1)
|
||||
expect(unlinkSpy).toHaveBeenCalledWith(tmpFilePath)
|
||||
app.dispose()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { Level, logger } from "@coder/logger"
|
||||
import { promises as fs } from "fs"
|
||||
import * as net from "net"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import {
|
||||
UserProvidedArgs,
|
||||
bindAddrFromArgs,
|
||||
defaultConfigFile,
|
||||
parse,
|
||||
readSocketPath,
|
||||
setDefaults,
|
||||
shouldOpenInExistingInstance,
|
||||
toCodeArgs,
|
||||
@@ -20,7 +17,12 @@ import {
|
||||
} from "../../../src/node/cli"
|
||||
import { shouldSpawnCliProcess } from "../../../src/node/main"
|
||||
import { generatePassword, paths } from "../../../src/node/util"
|
||||
import { clean, useEnv, tmpdir } from "../../utils/helpers"
|
||||
import {
|
||||
EditorSessionManager,
|
||||
EditorSessionManagerClient,
|
||||
makeEditorSessionManagerServer,
|
||||
} from "../../../src/node/vscodeSocket"
|
||||
import { clean, useEnv, tmpdir, listenOn } from "../../utils/helpers"
|
||||
|
||||
// The parser should not set any defaults so the caller can determine what
|
||||
// values the user actually set. These are only set after explicitly calling
|
||||
@@ -34,6 +36,7 @@ const defaults = {
|
||||
usingEnvHashedPassword: false,
|
||||
"extensions-dir": path.join(paths.data, "extensions"),
|
||||
"user-data-dir": paths.data,
|
||||
"session-socket": path.join(paths.data, "code-server-ipc.sock"),
|
||||
_: [],
|
||||
}
|
||||
|
||||
@@ -43,6 +46,8 @@ describe("parser", () => {
|
||||
delete process.env.PASSWORD
|
||||
delete process.env.CS_DISABLE_FILE_DOWNLOADS
|
||||
delete process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE
|
||||
delete process.env.VSCODE_PROXY_URI
|
||||
delete process.env.CS_DISABLE_PROXY
|
||||
console.log = jest.fn()
|
||||
})
|
||||
|
||||
@@ -99,6 +104,10 @@ describe("parser", () => {
|
||||
|
||||
"--disable-getting-started-override",
|
||||
|
||||
"--disable-proxy",
|
||||
|
||||
["--session-socket", "/tmp/override-code-server-ipc-socket"],
|
||||
|
||||
["--host", "0.0.0.0"],
|
||||
"4",
|
||||
"--",
|
||||
@@ -117,6 +126,7 @@ describe("parser", () => {
|
||||
},
|
||||
"disable-file-downloads": true,
|
||||
"disable-getting-started-override": true,
|
||||
"disable-proxy": true,
|
||||
enable: ["feature1", "feature2"],
|
||||
help: true,
|
||||
host: "0.0.0.0",
|
||||
@@ -132,6 +142,7 @@ describe("parser", () => {
|
||||
"welcome-text": "welcome to code",
|
||||
version: true,
|
||||
"bind-addr": "192.169.0.1:8080",
|
||||
"session-socket": "/tmp/override-code-server-ipc-socket",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -385,6 +396,30 @@ describe("parser", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("should use env var CS_DISABLE_PROXY", async () => {
|
||||
process.env.CS_DISABLE_PROXY = "1"
|
||||
const args = parse([])
|
||||
expect(args).toEqual({})
|
||||
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
"disable-proxy": true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should use env var CS_DISABLE_PROXY set to true", async () => {
|
||||
process.env.CS_DISABLE_PROXY = "true"
|
||||
const args = parse([])
|
||||
expect(args).toEqual({})
|
||||
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
"disable-proxy": true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should error if password passed in", () => {
|
||||
expect(() => parse(["--password", "supersecret123"])).toThrowError(
|
||||
"--password can only be set in the config file or passed in via $PASSWORD",
|
||||
@@ -412,7 +447,7 @@ describe("parser", () => {
|
||||
const defaultArgs = await setDefaults(args)
|
||||
expect(defaultArgs).toEqual({
|
||||
...defaults,
|
||||
"proxy-domain": ["coder.com", "coder.org"],
|
||||
"proxy-domain": ["{{port}}.coder.com", "{{port}}.coder.org"],
|
||||
})
|
||||
})
|
||||
it("should allow '=,$/' in strings", async () => {
|
||||
@@ -457,11 +492,36 @@ describe("parser", () => {
|
||||
port: 8082,
|
||||
})
|
||||
})
|
||||
|
||||
it("should not set proxy uri", async () => {
|
||||
await setDefaults(parse([]))
|
||||
expect(process.env.VSCODE_PROXY_URI).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should set proxy uri", async () => {
|
||||
await setDefaults(parse(["--proxy-domain", "coder.org"]))
|
||||
expect(process.env.VSCODE_PROXY_URI).toEqual("//{{port}}.coder.org")
|
||||
})
|
||||
|
||||
it("should set proxy uri to first domain", async () => {
|
||||
await setDefaults(
|
||||
parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "coder.com", "--proxy-domain", "coder.org"]),
|
||||
)
|
||||
expect(process.env.VSCODE_PROXY_URI).toEqual("//{{port}}.coder.com")
|
||||
})
|
||||
|
||||
it("should not override existing proxy uri", async () => {
|
||||
process.env.VSCODE_PROXY_URI = "foo"
|
||||
await setDefaults(
|
||||
parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "coder.com", "--proxy-domain", "coder.org"]),
|
||||
)
|
||||
expect(process.env.VSCODE_PROXY_URI).toEqual("foo")
|
||||
})
|
||||
})
|
||||
|
||||
describe("cli", () => {
|
||||
const testName = "cli"
|
||||
const vscodeIpcPath = path.join(os.tmpdir(), "vscode-ipc")
|
||||
let tmpDirPath: string
|
||||
|
||||
beforeAll(async () => {
|
||||
await clean(testName)
|
||||
@@ -469,68 +529,170 @@ describe("cli", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
delete process.env.VSCODE_IPC_HOOK_CLI
|
||||
await fs.rm(vscodeIpcPath, { force: true, recursive: true })
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
})
|
||||
|
||||
it("should use existing if inside code-server", async () => {
|
||||
process.env.VSCODE_IPC_HOOK_CLI = "test"
|
||||
const args: UserProvidedArgs = {}
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
|
||||
|
||||
args.port = 8081
|
||||
args._ = ["./file"]
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
|
||||
})
|
||||
|
||||
it("should use existing if --reuse-window is set", async () => {
|
||||
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
args["reuse-window"] = true
|
||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(undefined)
|
||||
await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
|
||||
|
||||
await fs.writeFile(vscodeIpcPath, "test")
|
||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
|
||||
const socketPath = path.join(tmpDirPath, "socket")
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath,
|
||||
},
|
||||
})
|
||||
const vscodeSockets = listenOn(socketPath)
|
||||
|
||||
await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
|
||||
|
||||
args.port = 8081
|
||||
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
|
||||
await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
|
||||
|
||||
server.close()
|
||||
vscodeSockets.close()
|
||||
})
|
||||
|
||||
it("should use existing if --new-window is set", async () => {
|
||||
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
args["new-window"] = true
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
|
||||
|
||||
await fs.writeFile(vscodeIpcPath, "test")
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
const socketPath = path.join(tmpDirPath, "socket")
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath,
|
||||
},
|
||||
})
|
||||
const vscodeSockets = listenOn(socketPath)
|
||||
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||
|
||||
args.port = 8081
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||
|
||||
server.close()
|
||||
vscodeSockets.close()
|
||||
})
|
||||
|
||||
it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => {
|
||||
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||
|
||||
args._ = ["./file"]
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||
|
||||
const testDir = await tmpdir(testName)
|
||||
const socketPath = path.join(testDir, "socket")
|
||||
await fs.writeFile(vscodeIpcPath, socketPath)
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const server = net.createServer(() => {
|
||||
// Close after getting the first connection.
|
||||
server.close()
|
||||
})
|
||||
server.once("listening", () => resolve(server))
|
||||
server.listen(socketPath)
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
const socketPath = path.join(tmpDirPath, "socket")
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath,
|
||||
},
|
||||
})
|
||||
const vscodeSockets = listenOn(socketPath)
|
||||
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath)
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
|
||||
|
||||
args.port = 8081
|
||||
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
|
||||
|
||||
server.close()
|
||||
vscodeSockets.close()
|
||||
})
|
||||
|
||||
it("should prefer matching sessions for only the first path", async () => {
|
||||
const sessionSocket = path.join(tmpDirPath, "session-socket")
|
||||
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
|
||||
const client = new EditorSessionManagerClient(sessionSocket)
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
},
|
||||
})
|
||||
await client.addSession({
|
||||
entry: {
|
||||
workspace: {
|
||||
id: "bbb",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/bbb",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
},
|
||||
})
|
||||
listenOn(`${tmpDirPath}/vscode-ipc-aaa.sock`, `${tmpDirPath}/vscode-ipc-bbb.sock`)
|
||||
|
||||
const args: UserProvidedArgs = {}
|
||||
args._ = ["/aaa/file", "/bbb/file"]
|
||||
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(`${tmpDirPath}/vscode-ipc-aaa.sock`)
|
||||
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -703,44 +865,6 @@ cert: false`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("readSocketPath", () => {
|
||||
const fileContents = "readSocketPath file contents"
|
||||
let tmpDirPath: string
|
||||
let tmpFilePath: string
|
||||
|
||||
const testName = "readSocketPath"
|
||||
beforeAll(async () => {
|
||||
await clean(testName)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
tmpFilePath = path.join(tmpDirPath, "readSocketPath.txt")
|
||||
await fs.writeFile(tmpFilePath, fileContents)
|
||||
})
|
||||
|
||||
it("should throw an error if it can't read the file", async () => {
|
||||
// TODO@jsjoeio - implement
|
||||
// Test it on a directory.... ESDIR
|
||||
// TODO@jsjoeio - implement
|
||||
expect(() => readSocketPath(tmpDirPath)).rejects.toThrow("EISDIR")
|
||||
})
|
||||
it("should return undefined if it can't read the file", async () => {
|
||||
// TODO@jsjoeio - implement
|
||||
const socketPath = await readSocketPath(path.join(tmpDirPath, "not-a-file"))
|
||||
expect(socketPath).toBeUndefined()
|
||||
})
|
||||
it("should return the file contents", async () => {
|
||||
const contents = await readSocketPath(tmpFilePath)
|
||||
expect(contents).toBe(fileContents)
|
||||
})
|
||||
it("should return the same file contents for two different calls", async () => {
|
||||
const contents1 = await readSocketPath(tmpFilePath)
|
||||
const contents2 = await readSocketPath(tmpFilePath)
|
||||
expect(contents2).toBe(contents1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("toCodeArgs", () => {
|
||||
const vscodeDefaults = {
|
||||
...defaults,
|
||||
|
||||
@@ -24,37 +24,41 @@ describe("http", () => {
|
||||
{
|
||||
origin: "",
|
||||
host: "",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
origin: "http://localhost:8080",
|
||||
host: "",
|
||||
expected: false,
|
||||
expected: "no host headers",
|
||||
},
|
||||
{
|
||||
origin: "http://localhost:8080",
|
||||
host: " ",
|
||||
expected: "does not match",
|
||||
},
|
||||
{
|
||||
origin: "http://localhost:8080",
|
||||
host: "localhost:8080",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
origin: "http://localhost:8080",
|
||||
host: "localhost:8081",
|
||||
expected: false,
|
||||
expected: "does not match",
|
||||
},
|
||||
{
|
||||
origin: "localhost:8080",
|
||||
host: "localhost:8080",
|
||||
expected: false, // Gets parsed as host: localhost and path: 8080.
|
||||
expected: "does not match", // Gets parsed as host: localhost and path: 8080.
|
||||
},
|
||||
{
|
||||
origin: "test.org",
|
||||
host: "localhost:8080",
|
||||
expected: false, // Parsing fails completely.
|
||||
expected: "malformed", // Parsing fails completely.
|
||||
},
|
||||
].forEach((test) => {
|
||||
;[
|
||||
["host", test.host],
|
||||
["x-forwarded-host", test.host],
|
||||
["x-forwarded-host", `${test.host}, ${test.host}`],
|
||||
["forwarded", `for=127.0.0.1, host=${test.host}, proto=http`],
|
||||
["forwarded", `for=127.0.0.1;proto=http;host=${test.host}`],
|
||||
["forwarded", `proto=http;host=${test.host}, for=127.0.0.1`],
|
||||
@@ -66,8 +70,13 @@ describe("http", () => {
|
||||
origin: test.origin,
|
||||
[key]: value,
|
||||
},
|
||||
args: {},
|
||||
})
|
||||
expect(http.authenticateOrigin(req)).toBe(test.expected)
|
||||
if (typeof test.expected === "string") {
|
||||
expect(() => http.authenticateOrigin(req)).toThrow(test.expected)
|
||||
} else {
|
||||
expect(() => http.authenticateOrigin(req)).not.toThrow()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -43,6 +43,7 @@ describe("plugin", () => {
|
||||
usingEnvHashedPassword: false,
|
||||
"extensions-dir": "",
|
||||
"user-data-dir": "",
|
||||
"session-socket": "",
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
@@ -45,6 +45,17 @@ describe("proxy", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should return 403 Forbidden if proxy is disabled", async () => {
|
||||
e.get("/wsup", (req, res) => {
|
||||
res.json("you cannot see this")
|
||||
})
|
||||
codeServer = await integration.setup(["--auth=none", "--disable-proxy"], "")
|
||||
const resp = await codeServer.fetch(proxyPath)
|
||||
expect(resp.status).toBe(403)
|
||||
const json = await resp.json()
|
||||
expect(json).toEqual({ error: "Forbidden" })
|
||||
})
|
||||
|
||||
it("should rewrite the base path", async () => {
|
||||
e.get("/wsup", (req, res) => {
|
||||
res.json("asher is the best")
|
||||
@@ -187,6 +198,17 @@ describe("proxy", () => {
|
||||
})
|
||||
}).rejects.toThrow()
|
||||
})
|
||||
|
||||
it("should proxy non-ASCII", async () => {
|
||||
e.get("*", (req, res) => {
|
||||
res.json("ほげ")
|
||||
})
|
||||
codeServer = await integration.setup(["--auth=none"], "")
|
||||
const resp = await codeServer.fetch(proxyPath.replace("wsup", "ほげ"))
|
||||
expect(resp.status).toBe(200)
|
||||
const json = await resp.json()
|
||||
expect(json).toBe("ほげ")
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE@jsjoeio
|
||||
|
||||
277
test/unit/node/vscodeSocket.test.ts
Normal file
277
test/unit/node/vscodeSocket.test.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import { logger } from "@coder/logger"
|
||||
import * as app from "../../../src/node/app"
|
||||
import { EditorSessionManager, makeEditorSessionManagerServer } from "../../../src/node/vscodeSocket"
|
||||
import { clean, tmpdir, listenOn, mockLogger } from "../../utils/helpers"
|
||||
|
||||
describe("makeEditorSessionManagerServer", () => {
|
||||
let tmpDirPath: string
|
||||
|
||||
const testName = "mesms"
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
mockLogger()
|
||||
await clean(testName)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.resetModules()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
})
|
||||
|
||||
it("warns if socket cannot be created", async () => {
|
||||
jest.spyOn(app, "listen").mockImplementation(() => {
|
||||
throw new Error()
|
||||
})
|
||||
const server = await makeEditorSessionManagerServer(
|
||||
`${tmpDirPath}/code-server-ipc.sock`,
|
||||
new EditorSessionManager(),
|
||||
)
|
||||
expect(logger.warn).toHaveBeenCalledWith(`Could not create socket at ${tmpDirPath}/code-server-ipc.sock`)
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe("EditorSessionManager", () => {
|
||||
let tmpDirPath: string
|
||||
|
||||
const testName = "esm"
|
||||
|
||||
beforeAll(async () => {
|
||||
await clean(testName)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDirPath = await tmpdir(testName)
|
||||
})
|
||||
|
||||
describe("getCandidatesForFile", () => {
|
||||
it("should prefer the last added socket path for a matching path", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa-1.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa-2.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "bbb",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/bbb",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
})
|
||||
const socketPaths = manager.getCandidatesForFile("/aaa/some-file:1:1")
|
||||
expect(socketPaths.map((x) => x.socketPath)).toEqual([
|
||||
// Matches
|
||||
`${tmpDirPath}/vscode-ipc-aaa-2.sock`,
|
||||
`${tmpDirPath}/vscode-ipc-aaa-1.sock`,
|
||||
// Non-matches
|
||||
`${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
])
|
||||
})
|
||||
|
||||
it("should return the last added socketPath if there are no matches", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "bbb",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/bbb",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
})
|
||||
const socketPaths = manager.getCandidatesForFile("/ccc/some-file:1:1")
|
||||
expect(socketPaths.map((x) => x.socketPath)).toEqual([
|
||||
`${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
`${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
])
|
||||
})
|
||||
|
||||
it("does not just directly do a substring match", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "bbb",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/bbb",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
})
|
||||
const entries = manager.getCandidatesForFile("/aaaxxx/some-file:1:1")
|
||||
expect(entries.map((x) => x.socketPath)).toEqual([
|
||||
`${tmpDirPath}/vscode-ipc-bbb.sock`,
|
||||
`${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("getConnectedSocketPath", () => {
|
||||
it("should return socket path if socket is active", async () => {
|
||||
listenOn(`${tmpDirPath}/vscode-ipc-aaa.sock`).once()
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-aaa.sock`)
|
||||
})
|
||||
|
||||
it("should return undefined if socket is inactive", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return undefined given no matching active sockets", async () => {
|
||||
const vscodeSockets = listenOn(`${tmpDirPath}/vscode-ipc-bbb.sock`)
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
|
||||
})
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBeUndefined()
|
||||
vscodeSockets.close()
|
||||
})
|
||||
|
||||
it("should return undefined if there are no entries", async () => {
|
||||
const manager = new EditorSessionManager()
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return most recently used socket path available", async () => {
|
||||
listenOn(`${tmpDirPath}/vscode-ipc-aaa-1.sock`).once()
|
||||
const manager = new EditorSessionManager()
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa-1.sock`,
|
||||
})
|
||||
manager.addSession({
|
||||
workspace: {
|
||||
id: "aaa",
|
||||
folders: [
|
||||
{
|
||||
uri: {
|
||||
path: "/aaa",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
socketPath: `${tmpDirPath}/vscode-ipc-aaa-2.sock`,
|
||||
})
|
||||
|
||||
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
|
||||
expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-aaa-1.sock`)
|
||||
// Failed sockets should be removed from the entries.
|
||||
expect((manager as any).entries.has(`${tmpDirPath}/vscode-ipc-aaa-2.sock`)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -150,3 +150,52 @@ export function getMaybeProxiedPathname(url: URL): string {
|
||||
|
||||
return url.pathname
|
||||
}
|
||||
|
||||
interface FakeVscodeSockets {
|
||||
/* If called, closes all servers after the first connection. */
|
||||
once(): FakeVscodeSockets
|
||||
|
||||
/* Manually close all servers. */
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates servers for each socketPath specified.
|
||||
*/
|
||||
export function listenOn(...socketPaths: string[]): FakeVscodeSockets {
|
||||
let once = false
|
||||
const servers = socketPaths.map((socketPath) => {
|
||||
const server = net.createServer(() => {
|
||||
if (once) {
|
||||
close()
|
||||
}
|
||||
})
|
||||
server.listen(socketPath)
|
||||
return server
|
||||
})
|
||||
|
||||
async function close() {
|
||||
await Promise.all(
|
||||
servers.map(
|
||||
(server) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
const fakeVscodeSockets = {
|
||||
close,
|
||||
once: () => {
|
||||
once = true
|
||||
return fakeVscodeSockets
|
||||
},
|
||||
}
|
||||
return fakeVscodeSockets
|
||||
}
|
||||
|
||||
106
test/yarn.lock
106
test/yarn.lock
@@ -1285,16 +1285,16 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
||||
safe-buffer "~5.1.1"
|
||||
|
||||
cookiejar@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc"
|
||||
integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
|
||||
integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
|
||||
|
||||
cross-fetch@^3.0.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
|
||||
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
|
||||
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
|
||||
dependencies:
|
||||
node-fetch "2.6.1"
|
||||
node-fetch "2.6.7"
|
||||
|
||||
cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
@@ -2334,9 +2334,9 @@ jest@^27.3.1:
|
||||
jest-cli "^27.4.7"
|
||||
|
||||
jpeg-js@^0.4.2:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b"
|
||||
integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa"
|
||||
integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==
|
||||
|
||||
js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -2395,11 +2395,9 @@ json-schema@^0.4.0:
|
||||
integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
|
||||
|
||||
json5@2.x, json5@^2.1.2:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
|
||||
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
kleur@^3.0.3:
|
||||
version "3.0.3"
|
||||
@@ -2503,17 +2501,12 @@ mimic-fn@^2.1.0:
|
||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
|
||||
minimatch@^3.0.3, 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==
|
||||
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"
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
minipass@^3.0.0:
|
||||
version "3.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
|
||||
@@ -2554,19 +2547,7 @@ 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-fetch@2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.6"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
|
||||
integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^2.6.5:
|
||||
node-fetch@2.6.7, node-fetch@^2.6.5, node-fetch@^2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
@@ -2875,6 +2856,11 @@ qs@^6.10.1:
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
react-is@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
@@ -2894,6 +2880,11 @@ require-directory@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
|
||||
|
||||
resolve-cwd@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
|
||||
@@ -2955,16 +2946,16 @@ saxes@^5.0.1:
|
||||
xmlchars "^2.2.0"
|
||||
|
||||
semver@7.x, semver@^7.3.2, semver@^7.3.5:
|
||||
version "7.3.5"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
|
||||
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^6.0.0, semver@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
version "6.3.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
set-blocking@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -3241,13 +3232,14 @@ to-regex-range@^5.0.1:
|
||||
is-number "^7.0.0"
|
||||
|
||||
tough-cookie@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
|
||||
integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
|
||||
integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
|
||||
dependencies:
|
||||
psl "^1.1.33"
|
||||
punycode "^2.1.1"
|
||||
universalify "^0.1.2"
|
||||
universalify "^0.2.0"
|
||||
url-parse "^1.5.3"
|
||||
|
||||
tr46@^2.1.0:
|
||||
version "2.1.0"
|
||||
@@ -3299,10 +3291,18 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
universalify@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
|
||||
|
||||
url-parse@^1.5.3:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
@@ -3398,9 +3398,9 @@ wide-align@^1.1.2:
|
||||
string-width "^1.0.2 || 2 || 3 || 4"
|
||||
|
||||
word-wrap@~1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f"
|
||||
integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
|
||||
Reference in New Issue
Block a user