Compare commits

..

61 Commits

Author SHA1 Message Date
Asher
f51e045cd5 Use the nbin centos container to build again
This will put the glibc requirement back down to what it used to be.
2020-01-17 16:27:36 -06:00
Asher
8122b7f69e Remove unused upload service
No longer needed since VS Code has their own now.
2020-01-17 12:23:36 -06:00
Asher
25f18beda4 Fix version test 2020-01-16 18:09:19 -06:00
Asher
7e7923706f Fix version generated from Git tag 2020-01-16 17:59:11 -06:00
Asher
ae35673489 Use custom Yarn cache directory
Makes it easier to upload and restore.
2020-01-16 15:39:44 -06:00
Asher
23f142fdc6 Cache Yarn cache 2020-01-16 15:23:25 -06:00
Asher
101139fabf Fix Drone CI releases
Also skip the 32 bit arm releases since they don't currently build
anyway.
2020-01-16 14:53:59 -06:00
Asher
e2d354c8f2 Move manifest icon to the root as well 2020-01-16 12:11:56 -06:00
Asher
7c178805ea Add comment about the manifest's served location
Also for #1278.
2020-01-16 11:44:17 -06:00
Asher
45f70e741f Move manifest to the root
Fixes #1278.
2020-01-16 11:36:17 -06:00
Asher
1474a82c7d Add insecure access notification 2020-01-16 11:15:22 -06:00
Asher
d97feca3ba Add code-server version to the about dialog 2020-01-15 18:02:19 -06:00
Asher
b2669e78bf Implement ExtHostStoragePaths for the browser
This appears to make vscodevim work again.
2020-01-15 17:13:06 -06:00
Asher
66ee6e8201 Ignore 32 bit arm failures for now
Seems we are running out of memory.
2020-01-15 13:48:27 -06:00
Asher
62f050fda7 Add a simple test 2020-01-15 13:22:45 -06:00
Asher
57425377e5 Use CI dockerfile for pushing Docker image 2020-01-15 13:22:45 -06:00
Asher
174cb2f8a9 Remove unused Docker step from CI script 2020-01-15 13:22:42 -06:00
Asher
42bddce21f Add defaults for environment variables
So we don't have to keep setting them for each CI and every single step
since there doesn't seem to be a way to share them between steps in
Drone.
2020-01-15 13:21:58 -06:00
Asher
f2a15795a1 Use draft releases for Drone
This gives us a chance to review it and add notes.
2020-01-15 13:21:58 -06:00
Asher
6dd5e515c5 Travis release on tags only and remove Docker push
The manual tagging is necessary to sync up the releases of the two
different CIs.
2020-01-15 13:21:55 -06:00
Asher
92da02ef3e Add Drone CI caching 2020-01-15 13:20:58 -06:00
Ayane Satomi
3ce7129492 Drone CI migration (#1261) 2020-01-15 13:14:05 -06:00
Asher
336ee28888 Update Node to 12.14.0 2020-01-08 16:30:44 -06:00
Asher
3f2240ab65 Update logger 2020-01-08 16:30:34 -06:00
Asher
1087037728 Don't push latest and v2 Docker tags automatically
We should only push those when the version is confirmed to work.
2020-01-08 15:05:12 -06:00
Asher
1959d82912 Increase cache timeout
The Mac build seems to be terminated due to a timeout during the caching
stage.
2020-01-08 13:20:50 -06:00
Asher
8024144381 Update VS Code to 1.41.1 2020-01-07 18:27:41 -06:00
Asher
6a1dcab7a6 Update nbin
Should finally be able to build with Node v12 now.
2020-01-07 18:27:28 -06:00
Asher
e6d1f2a7c8 Update VS Code to 1.41.0 2019-12-16 16:52:29 -06:00
Asher
44c4722edf Fix data directory path in Dockerfile 2019-12-10 12:06:52 -06:00
Asher
e5fc63f2c8 Fix accessing manifest behind basic auth
Apparently the manifest spec doesn't include sending credentials in an
attempt to be secure by default.

Fixes #1212.
2019-12-09 11:25:59 -06:00
Asher
015a99e87d Always install VS Code dependencies
This fixes the case where the script is killed before all the
dependencies were fully installed.
2019-12-09 10:55:24 -06:00
Simen Eriksen
884491d72b Update Dockerfile to fix EACCES issue on mount (#1191)
https://github.com/cdr/code-server/issues/1188 
Fixes issue with permissions mounting in directories in the container. Folders are generated by root causing issues when the container user "coder" wants to create sub-folders. This fix solves it, at least on Crostini (ChromeOS)
2019-12-05 13:38:03 -06:00
Asher
e14362f322 Pass along Node options 2019-11-14 17:20:23 -06:00
Asher
917aa48072 Update enterprise link
Fixes #1172.
2019-11-14 11:16:08 -06:00
Asher
938c6ef829 Update fail2ban configuration
Fixes #1177.
2019-11-14 11:14:27 -06:00
Sandro
0add01d383 Delete apt lists from final image (#1174) 2019-11-14 11:12:21 -06:00
Asher
2018024810 Hash password
Fixes issues with unexpected characters breaking things when setting the
cookie (like semicolons).

This change as-is does not affect the security of code-server
itself (we've just replaced the static password with a static hash) but
if we were to add a salt in the future it would let us invalidate keys
by rehashing with a new salt which could be handy.
2019-11-07 15:57:57 -06:00
Asher
a1d6bcb8e5 Handle cookies more robustly
If you visit /login/ instead of /login the cookie will be set at /login
instead of / which means the cookie can't be read at the root. It will
redirect to the login page which *can* read the cookie at /login and
redirect back resulting in an infinite loop.

The previous solution relied on setting the cookie at / (any invalid
value works) which then overrode the login page cookie since
parseCookies only kept a single value. So the login page would see the
same cookie the root was seeing and not redirect back. However, that
behavior depends on the cookies being in the right order which I'm not
sure is guaranteed.

This new method tests all available cookies and always sets the cookie
so the root path will be able to read it in case the login page is
seeing a cookie the root can't.

It also goes a step further and explicitly sets the path on the cookie
which fixes the case where there is a permanent misconfiguration
redirecting /login to /login/. Otherwise the cookie would continually be
set on /login only and you'd have another loop. It also means you only
need to delete one cookie to log out.

Lastly add some properties to make the cookies a bit more secure.
2019-11-07 13:36:18 -06:00
ecrode
727ac6483b Clear password when redirecting to login
Should prevent endless redirects when the cookie is set on a different path or domain (like with a dot prefix).
2019-11-07 11:38:10 -06:00
Asher
2c15c09fc0 Add missing telemetry option 2019-11-06 15:47:34 -06:00
Asher
2ad2582cc0 Minor readme updates and fixes 2019-11-05 13:49:18 -06:00
Asher
cee0ac213c Fix error activating extensions on insecure domains
Doesn't affect Firefox but it does affect other browsers.

Fixes #1136.
2019-11-04 17:10:00 -06:00
Asher
780a673017 Add meta tag to allow full screen app on iOS
Fixes #933.
2019-11-04 16:01:01 -06:00
Asher
af71203955 Fix relaunching during an update 2019-11-01 10:51:23 -05:00
Asher
fc3acfabb2 Fix update check 2019-10-30 17:35:50 -05:00
Asher
3d5db8313a Add secure domain to requirements 2019-10-30 10:33:07 -05:00
Asher
73cf8f34e3 Fix outgoing scheme transformation
Accidentally used local instead of remote.

Fixes #1127.
2019-10-30 10:32:57 -05:00
dependabot[bot]
766efd6079 Bump mixin-deep from 1.3.1 to 1.3.2 (#1126)
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-10-29 15:20:12 -05:00
Asher
87485948ad Kill inner process if parent process dies
Fixes #1076.
2019-10-29 14:43:27 -05:00
Asher
7e4a73ce2d Fix schema matching against vscode-remote
Fixes #1104.
2019-10-29 11:42:28 -05:00
Asher
2f0878d9b7 Revert remote scheme change
It doesn't show in the explorer anymore so there's no point. Also remove
the local scheme transform which is no longer required with the latest
client-side extension implementation.
2019-10-29 11:26:50 -05:00
Marc-André Daigneault
f65c9b23fc Add docker-compose file (#680) 2019-10-29 11:08:01 -05:00
Asher
cd859d117f Start pushing to latest Docker tag 2019-10-29 11:04:38 -05:00
Asher
e22964915a Support opening workspaces from command line
Partly addresses #1121.
2019-10-28 16:25:51 -05:00
Asher
197d0b6ca9 Strip internal env vars when spawning the shell
This should fix all those reports of code-server dropping straight to
Node and things like #1121.
2019-10-28 16:08:32 -05:00
Asher
422503ef98 Proxy child exit code when exiting parent process
This fixes code-server exiting with zero on errors.
2019-10-28 14:57:01 -05:00
Asher
ea36345d2c Allow fetching any resource
Fixes #1118.
2019-10-28 14:29:51 -05:00
Asher
a89d83cbba Fix other incorrect usages of split 2019-10-28 14:03:13 -05:00
Asher
83ff31b620 Fix passwords that contain =
Fixes #1119.

Apparently `split` does not work the way I'd expect.
2019-10-28 13:47:31 -05:00
Asher
3a9b032c72 Add heartbeat file (#1115)
Fixes #1050.
2019-10-28 09:59:34 -05:00
36 changed files with 2285 additions and 1342 deletions

View File

@@ -9,4 +9,4 @@ doc
LICENSE LICENSE
README.md README.md
node_modules node_modules
release release

368
.drone.yml Normal file
View File

@@ -0,0 +1,368 @@
kind: pipeline
type: docker
name: amd64:linux
platform:
arch: amd64
steps:
- name: cache:restore
image: node:12
commands:
- ./scripts/cacher.sh
- name: build
image: codercom/nbin:centos
commands:
- yum install -y libxkbfile-devel libsecret-devel
- . /opt/rh/devtoolset-6/enable
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12
commands:
- yarn test
- name: publish:github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files: release/*.tar.gz
draft: true
overwrite: true
title: ${DRONE_TAG}
when:
event: tag
- name: publish:docker
image: plugins/docker
settings:
username:
from_secret: docker_user
password:
from_secret: docker_pass
repo: codercom/code-server
dockerfile: scripts/ci.dockerfile
tags:
- ${DRONE_TAG}
when:
event: tag
---
kind: pipeline
type: docker
name: amd64:alpine
platform:
arch: amd64
steps:
- name: cache:restore
image: node:12-alpine
commands:
- ./scripts/cacher.sh
- name: build
image: node:12-alpine
commands:
- apk add libxkbfile-dev libsecret-dev build-base git bash python
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12-alpine
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12-alpine
commands:
- yarn test
- name: publish:github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files: release/*.tar.gz
draft: true
overwrite: true
title: ${DRONE_TAG}
when:
event: tag
---
kind: pipeline
type: docker
name: arm64:linux
platform:
arch: arm64
steps:
- name: cache:restore
image: node:12
commands:
- ./scripts/cacher.sh
- name: build
image: node:12
commands:
- apt update && apt install -y build-essential git libsecret-1-dev libx11-dev libxkbfile-dev
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12
commands:
- yarn test
- name: publish:github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files: release/*.tar.gz
draft: true
overwrite: true
title: ${DRONE_TAG}
when:
event: tag
- name: publish:docker
image: plugins/docker
settings:
username:
from_secret: docker_user
password:
from_secret: docker_pass
repo: codercom/code-server
dockerfile: scripts/ci.dockerfile
tags:
- ${DRONE_TAG}-arm64
when:
event: tag
---
kind: pipeline
type: docker
name: arm64:alpine
platform:
arch: arm64
steps:
- name: cache:restore
image: node:12-alpine
commands:
- ./scripts/cacher.sh
- name: build
image: node:12-alpine
commands:
- apk add libxkbfile-dev libsecret-dev build-base git bash python
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12-alpine
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12-alpine
commands:
- yarn test
- name: publish:github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files: release/*.tar.gz
draft: true
overwrite: true
title: ${DRONE_TAG}
when:
event: tag
---
kind: pipeline
type: docker
name: arm:linux
platform:
arch: arm
steps:
- name: cache:restore
image: node:12
commands:
- ./scripts/cacher.sh
- name: build
image: node:12
commands:
- apt update && apt install -y build-essential git libsecret-1-dev libx11-dev libxkbfile-dev
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12
failure: ignore
commands:
- yarn test
# - name: publish:github
# image: plugins/github-release
# settings:
# api_key:
# from_secret: github_token
# files: release/*.tar.gz
# draft: true
# overwrite: true
# title: ${DRONE_TAG}
# when:
# event: tag
# - name: publish:docker
# image: plugins/docker
# settings:
# username:
# from_secret: docker_user
# password:
# from_secret: docker_pass
# repo: codercom/code-server
# dockerfile: scripts/ci.dockerfile
# tags:
# - ${DRONE_TAG}-arm
# when:
# event: tag
---
kind: pipeline
type: docker
name: arm:alpine
platform:
arch: arm
steps:
- name: cache:restore
image: node:12-alpine
commands:
- ./scripts/cacher.sh
- name: build
image: node:12-alpine
commands:
- apk add libxkbfile-dev libsecret-dev build-base git bash python
- timeout 50m ./scripts/ci.bash || echo 'Timed out or failed; continuing so we can preserve cache for the next run'
- name: cache:package
image: node:12-alpine
commands:
- ./scripts/cacher.sh
when:
event: push
- name: cache:push
image: plugins/gcs
settings:
source: cache-upload/
target: codesrv-ci.cdr.sh
token:
from_secret: gcs-token
when:
event: push
- name: test
image: node:12-alpine
failure: ignore
commands:
- yarn test
# - name: publish:github
# image: plugins/github-release
# failure: ignore
# settings:
# api_key:
# from_secret: github_token
# files: release/*.tar.gz
# draft: true
# overwrite: true
# title: ${DRONE_TAG}
# when:
# event: tag

8
.gitignore vendored
View File

@@ -1,5 +1,5 @@
node_modules node_modules
build /build
release /release
binaries /binaries
source /lib

View File

@@ -1 +1 @@
10.16.0 12.14.0

View File

@@ -1,54 +1,25 @@
language: node_js language: node_js
node_js: node_js:
- 10.16.0 - 12.14.0
services: services:
- docker - docker
before_install:
- export MAJOR_VERSION="2"
- export VSCODE_VERSION="1.39.2"
- export VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER"
- export TAG="$VERSION-vsc$VSCODE_VERSION"
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then export MINIFY="true"; fi
- if [[ "$TRAVIS_BRANCH" == "master" ]]; then export PACKAGE="true"; fi
# Don't build on tags because we'll already have built the commit.
jobs: jobs:
include: include:
- name: "Linux build"
os: linux
dist: trusty
env: TARGET="linux" PUSH_DOCKER="true"
if: tag IS blank
script: scripts/ci.bash
- name: "Alpine build"
os: linux
dist: trusty
env: TARGET="alpine"
if: tag IS blank
script: scripts/ci.bash
- name: "MacOS build" - name: "MacOS build"
os: osx os: osx
if: tag IS blank
script: travis_wait 60 scripts/ci.bash script: travis_wait 60 scripts/ci.bash
git: git:
depth: 3 depth: 3
before_deploy:
- echo "$TAG" "$TRAVIS_COMMIT"
- git config --local user.name "$USER_NAME"
- git config --local user.email "$USER_EMAIL"
- if ! git tag "$TAG" "$TRAVIS_COMMIT" ; then echo "$TAG already exists"; fi
- if [[ -n "$PUSH_DOCKER" ]] ; then echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin ; fi
deploy: deploy:
- provider: releases - provider: releases
file_glob: true file_glob: true
draft: true draft: true
tag_name: "$TAG" tag_name: "$TRAVIS_TAG"
target_commitish: "$TRAVIS_COMMIT" target_commitish: "$TRAVIS_COMMIT"
name: "$TAG" name: "$TRAVIS_TAG"
skip_cleanup: true skip_cleanup: true
api_key: api_key:
secure: YL/x24KjYjgYXPcJWk3FV7FGxI79Mh6gBECQEcdlf3fkLEoKFVgzHBoUNWrFPzyR4tgLyWNAgcpD9Lkme1TRWTom7UPjXcwMNyLcLa+uec7ciSAnYD9ntLTpiCuPDD1u0LtRGclSi/EHQ+F8YVq+HZJpXTsJeAmOmihma3GVbGKSZr+BRum+0YZSG4w+o4TOlYzw/4bLWS52MogZcwpjd+hemBbgXLuGU2ziKv2vEKCZFbEeA16II4x1WLI4mutDdCeh7+3aLzGLwDa49NxtsVYNjyNFF75JhCTCNA55e2YMiLz9Uq69IXe/mi5F7xUaFfhIqqLNyKBnKeEOzu3dYnc+8n3LjnQ+00PmkF05nx9kBn3UfV1kwQGh6QbyDmTtBP07rtUMyI14aeQqHjxsaVRdMnwj9Q2DjXRr8UDqESZF0rmK3pHCXS2fBhIzLE8tLVW5Heiba2pQRFMHMZW+KBE97FzcFh7is90Ait3T8enfcd/PWFPYoBejDAdjwxwOkezh5N5ZkYquEfDYuWrFi6zRFCktsruaAcA+xGtTf9oilBBzUqu8Ie+YFWH5me83xakcblJWdaW/D2rLJAJH3m6LFm8lBqyUgDX5t/etob6CpDuYHu5D1J3XINOj/+aLAcadq6qlh70PMZS3zYffUu3JlzaD2amlSHIT8b5YXFc= secure: YL/x24KjYjgYXPcJWk3FV7FGxI79Mh6gBECQEcdlf3fkLEoKFVgzHBoUNWrFPzyR4tgLyWNAgcpD9Lkme1TRWTom7UPjXcwMNyLcLa+uec7ciSAnYD9ntLTpiCuPDD1u0LtRGclSi/EHQ+F8YVq+HZJpXTsJeAmOmihma3GVbGKSZr+BRum+0YZSG4w+o4TOlYzw/4bLWS52MogZcwpjd+hemBbgXLuGU2ziKv2vEKCZFbEeA16II4x1WLI4mutDdCeh7+3aLzGLwDa49NxtsVYNjyNFF75JhCTCNA55e2YMiLz9Uq69IXe/mi5F7xUaFfhIqqLNyKBnKeEOzu3dYnc+8n3LjnQ+00PmkF05nx9kBn3UfV1kwQGh6QbyDmTtBP07rtUMyI14aeQqHjxsaVRdMnwj9Q2DjXRr8UDqESZF0rmK3pHCXS2fBhIzLE8tLVW5Heiba2pQRFMHMZW+KBE97FzcFh7is90Ait3T8enfcd/PWFPYoBejDAdjwxwOkezh5N5ZkYquEfDYuWrFi6zRFCktsruaAcA+xGtTf9oilBBzUqu8Ie+YFWH5me83xakcblJWdaW/D2rLJAJH3m6LFm8lBqyUgDX5t/etob6CpDuYHu5D1J3XINOj/+aLAcadq6qlh70PMZS3zYffUu3JlzaD2amlSHIT8b5YXFc=
@@ -57,17 +28,10 @@ deploy:
- release/*.zip - release/*.zip
on: on:
repo: cdr/code-server repo: cdr/code-server
branch: master tags: true
- provider: script
skip_cleanup: true
script: docker build -f ./scripts/ci.dockerfile -t codercom/code-server:"$TAG" -t codercom/code-server:v2 . && docker push codercom/code-server:"$TAG" && docker push codercom/code-server:v2
on:
repo: cdr/code-server
branch: master
condition: -n "$PUSH_DOCKER"
cache: cache:
timeout: 1000
yarn: true yarn: true
directories: directories:
- source - source

View File

@@ -1,6 +1,5 @@
FROM node:10.16.0 FROM node:12.14.0
ARG codeServerVersion=docker ARG tag
ARG vscodeVersion
ARG githubToken ARG githubToken
# Install VS Code's deps. These are the only two it seems we need. # Install VS Code's deps. These are the only two it seems we need.
@@ -8,20 +7,15 @@ RUN apt-get update && apt-get install -y \
libxkbfile-dev \ libxkbfile-dev \
libsecret-1-dev libsecret-1-dev
# Ensure latest yarn.
RUN npm install -g yarn@1.13
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN yarn \ RUN yarn \
&& MINIFY=true GITHUB_TOKEN="${githubToken}" yarn build "${vscodeVersion}" "${codeServerVersion}" \ && DRONE_TAG="$tag" MINIFY=true BINARY=true GITHUB_TOKEN="$githubToken" ./scripts/ci.bash \
&& yarn binary "${vscodeVersion}" "${codeServerVersion}" \
&& mv "/src/binaries/code-server${codeServerVersion}-vsc${vscodeVersion}-linux-x86_64" /src/binaries/code-server \
&& rm -r /src/build \ && rm -r /src/build \
&& rm -r /src/source && rm -r /src/source
# We deploy with ubuntu so that devs have a familiar environment. # We deploy with Ubuntu so that devs have a familiar environment.
FROM ubuntu:18.04 FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
@@ -33,7 +27,8 @@ RUN apt-get update && apt-get install -y \
dumb-init \ dumb-init \
vim \ vim \
curl \ curl \
wget wget \
&& rm -rf /var/lib/apt/lists/*
RUN locale-gen en_US.UTF-8 RUN locale-gen en_US.UTF-8
# We cannot use update-locale because docker will not use the env variables # We cannot use update-locale because docker will not use the env variables
@@ -45,9 +40,10 @@ RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
USER coder USER coder
# We create first instead of just using WORKDIR as when WORKDIR creates, the # Create first so these directories will be owned by coder instead of root
# user is root. # (workdir and mounting appear to both default to root).
RUN mkdir -p /home/coder/project RUN mkdir -p /home/coder/project
RUN mkdir -p /home/coder/.local/share/code-server
WORKDIR /home/coder/project WORKDIR /home/coder/project

View File

@@ -22,10 +22,11 @@ docker run -it -p 127.0.0.1:8080:8080 -v "${HOME}/.local/share/code-server:/home
### Requirements ### Requirements
- Minimum GLIBC version of 2.17 and a minimum version of GLIBCXX of 3.4.15. - 64-bit host.
- This is the main requirement for building Visual Studio Code. We cannot go lower than this. - At least 1GB of RAM.
- A 64-bit host with at least 1GB RAM and 2 cores. - 2 cores or more are recommended (1 core works but not optimally).
- 1 core hosts would work but not optimally. - Secure connection over HTTPS or localhost (required for service workers).
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
- Docker (for Docker versions of `code-server`). - Docker (for Docker versions of `code-server`).
### Run over SSH ### Run over SSH
@@ -59,15 +60,15 @@ arguments when launching code-server with Docker. See
### Build ### Build
See See
[VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites) [VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
before building. before building.
```shell ```shell
export OUT=/path/to/output/build # Optional if only building. Required if also developing. export OUT=/path/to/output/build # Optional if only building. Required if also developing.
yarn build ${vscodeVersion} ${codeServerVersion} # See travis.yml for the VS Code version to use. yarn build $vscodeVersion $codeServerVersion # See travis.yml for the VS Code version to use.
# The code-server version can be anything you want. # The code-server version can be anything you want.
node ~/path/to/output/build/out/vs/server/main.js # You can run the built JavaScript with Node. node /path/to/output/build/out/vs/server/main.js # You can run the built JavaScript with Node.
yarn binary ${vscodeVersion} ${codeServerVersion} # Or you can package it into a binary. yarn binary $vscodeVersion $codeServerVersion # Or you can package it into a binary.
``` ```
## Security ## Security
@@ -134,7 +135,7 @@ data collected to improve code-server.
### Development ### Development
See See
[VS Code prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites) [VS Code's prerequisites](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites)
before developing. before developing.
```shell ```shell
@@ -154,8 +155,7 @@ yarn start
``` ```
If you run into issues about a different version of Node being used, try running If you run into issues about a different version of Node being used, try running
`npm rebuild` in the VS Code directory and ignore the error at the end from `npm rebuild` in the VS Code directory.
`vscode-ripgrep`.
### Upgrading VS Code ### Upgrading VS Code
@@ -170,13 +170,10 @@ directory.
Our changes include: Our changes include:
- Change the remote schema to `code-server`.
- Allow multiple extension directories (both user and built-in). - Allow multiple extension directories (both user and built-in).
- Modify the loader, websocket, webview, service worker, and asset requests to - Modify the loader, websocket, webview, service worker, and asset requests to
use the URL of the page as a base (and TLS if necessary for the websocket). use the URL of the page as a base (and TLS if necessary for the websocket).
- Send client-side telemetry through the server. - Send client-side telemetry through the server.
- Add an upload service along with a file prefix to ignore for temporary files
created during upload.
- Make changing the display language work. - Make changing the display language work.
- Make it possible for us to load code on the client. - Make it possible for us to load code on the client.
- Make extensions work in the browser. - Make extensions work in the browser.
@@ -189,8 +186,8 @@ Our changes include:
## Enterprise ## Enterprise
Visit [our enterprise page](https://coder.com/enterprise) for more information Visit [our enterprise page](https://coder.com) for more information about our
about our enterprise offering. enterprise offering.
## Commercialization ## Commercialization

View File

@@ -2,11 +2,11 @@
[Definition] [Definition]
failregex = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"remoteAddress\":\"<HOST>\" failregex = ^Failed login attempt\s+{\"remoteAddress\":\"<HOST>\"
# Use this instead for proxies (ensure the proxy is configured to send the # Use this instead for proxies (ensure the proxy is configured to send the
# X-Forwarded-For header). # X-Forwarded-For header).
# failregex = ^INFO\s+Failed login attempt\s+{\"password\":\"(\\.|[^"])*\",\"xForwardedFor\":\"<HOST>\" # failregex = ^Failed login attempt\s+{\"xForwardedFor\":\"<HOST>\"
ignoreregex = ignoreregex =

View File

@@ -30,6 +30,6 @@ accessible from the internet (use localhost or block it in your firewall).
## Fail2Ban ## Fail2Ban
Fail2Ban allows for automatically banning and logging repeated failed Fail2Ban allows for automatically banning and logging repeated failed
authentication attempts for many applications through regex filters. A working authentication attempts for many applications through regex filters. A working
filter for code-server can be found in `./code-server.fail2ban.conf`. Once this filter for code-server can be found in `./examples/fail2ban.conf`. Once this
is installed and configured correctly, repeated failed login attempts should is installed and configured correctly, repeated failed login attempts should
automatically be banned from connecting to your server. automatically be banned from connecting to your server.

13
docker-compose.yml Normal file
View File

@@ -0,0 +1,13 @@
version: "3"
services:
code-server:
container_name: code-server
image: codercom/code-server
ports:
- "8080:8080"
volumes:
- "${PWD}:/home/coder/project"
- "${HOME}/.local/share/code-server:/home/coder/.local/share/code-server"
environment:
PASSWORD: ${PASSWORD}

View File

@@ -3,6 +3,7 @@
"scripts": { "scripts": {
"runner": "cd ./scripts && node --max-old-space-size=32384 -r ts-node/register ./build.ts", "runner": "cd ./scripts && node --max-old-space-size=32384 -r ts-node/register ./build.ts",
"start": "nodemon --watch ../../../out --verbose ../../../out/vs/server/main.js", "start": "nodemon --watch ../../../out --verbose ../../../out/vs/server/main.js",
"test": "./scripts/test.sh",
"watch": "cd ../../../ && yarn watch", "watch": "cd ../../../ && yarn watch",
"build": "yarn && yarn runner build", "build": "yarn && yarn runner build",
"package": "yarn runner package", "package": "yarn runner package",
@@ -11,7 +12,7 @@
"patch:apply": "cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch" "patch:apply": "cd ../../../ && git apply ./src/vs/server/scripts/vscode.patch"
}, },
"devDependencies": { "devDependencies": {
"@coder/nbin": "^1.2.2", "@coder/nbin": "^1.2.7",
"@types/fs-extra": "^8.0.1", "@types/fs-extra": "^8.0.1",
"@types/node": "^10.12.12", "@types/node": "^10.12.12",
"@types/pem": "^1.9.5", "@types/pem": "^1.9.5",
@@ -28,7 +29,7 @@
"safe-buffer": "^5.1.1" "safe-buffer": "^5.1.1"
}, },
"dependencies": { "dependencies": {
"@coder/logger": "^1.1.8", "@coder/logger": "^1.1.12",
"@coder/node-browser": "^1.0.6", "@coder/node-browser": "^1.0.6",
"@coder/requirefs": "^1.0.6", "@coder/requirefs": "^1.0.6",
"httpolyglot": "^0.1.2", "httpolyglot": "^0.1.2",

View File

@@ -169,13 +169,9 @@ class Builder {
}); });
} }
if (fs.existsSync(path.join(vscodeSourcePath, "node_modules"))) { await this.task("Installing VS Code dependencies", () => {
this.log("Using existing VS Code node_modules"); return util.promisify(cp.exec)("yarn", { cwd: vscodeSourcePath });
} else { });
await this.task("Installing VS Code dependencies", () => {
return util.promisify(cp.exec)("yarn", { cwd: vscodeSourcePath });
});
}
if (fs.existsSync(path.join(vscodeSourcePath, ".build/extensions"))) { if (fs.existsSync(path.join(vscodeSourcePath, ".build/extensions"))) {
this.log("Using existing built-in-extensions"); this.log("Using existing built-in-extensions");
@@ -343,26 +339,6 @@ class Builder {
]); ]);
}); });
// onigasm 2.2.2 has a bug that makes it broken for PHP files so use 2.2.1.
// https://github.com/NeekSandhu/onigasm/issues/17
await this.task("Applying onigasm PHP fix", async () => {
const onigasmPath = path.join(finalBuildPath, "node_modules/onigasm-umd");
const onigasmTmpPath = `${onigasmPath}-temp`;
await Promise.all([
fs.remove(onigasmPath),
fs.mkdir(onigasmTmpPath),
]);
await util.promisify(cp.exec)(`git clone "https://github.com/alexandrudima/onigasm-umd" "${onigasmPath}"`);
await util.promisify(cp.exec)("yarn", { cwd: onigasmPath });
await util.promisify(cp.exec)("yarn add --dev onigasm@2.2.1", { cwd: onigasmPath });
await util.promisify(cp.exec)("yarn package", { cwd: onigasmPath });
await Promise.all(["release", "LICENSE", "package.json"].map((fileName) => {
return fs.copy(path.join(onigasmPath, fileName), path.join(onigasmTmpPath, fileName));
}));
await fs.remove(onigasmPath);
await fs.move(onigasmTmpPath, onigasmPath);
});
this.log(`Final build: ${finalBuildPath}`); this.log(`Final build: ${finalBuildPath}`);
} }

53
scripts/cacher.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env sh
# cacher.sh -- Restore and rebuild cache.
# Cache paths are designed to work with multi-arch builds and are organized
# based on the branch or tag. The master branch cache is used as a fallback.
# This will download and package the cache but it will not upload it.
set -eu
# Try restoring from each argument in turn until we get something.
restore() {
for branch in "$@" ; do
if [ -n "$branch" ] ; then
cache_path="https://codesrv-ci.cdr.sh/cache/$branch/$tar.tar.gz"
if wget "$cache_path" ; then
tar xzvf "$tar.tar.gz"
break
fi
fi
done
}
# We need to cache the built-in extensions and Node modules. Everything inside
# the cache-upload directory will be uploaded as-is to the code-server bucket.
package() {
mkdir -p "cache-upload/cache/$1"
tar czfv "cache-upload/cache/$1/$tar.tar.gz" node_modules source yarn-cache
}
main() {
cd "$(dirname "$0")/.."
# Get the branch for this build.
branch=${DRONE_BRANCH:-${DRONE_SOURCE_BRANCH:-${DRONE_TAG:-}}}
# The cache will be named based on the arch, platform, and libc.
arch=$DRONE_STAGE_ARCH
platform=${PLATFORM:-linux}
case $DRONE_STAGE_NAME in
*alpine*) libc=musl ;;
* ) libc=glibc ;;
esac
tar="$platform-$arch-$libc"
# The action is determined by the name of the step.
case $DRONE_STEP_NAME in
*restore*) restore "$branch" "$DRONE_REPO_BRANCH" ;;
*rebuild*|*package*) package "$branch" ;;
*) exit 1 ;;
esac
}
main "$@"

View File

@@ -1,78 +1,48 @@
#!/bin/bash #!/usr/bin/env bash
# ci.bash -- Build code-server in the CI.
set -euo pipefail set -euo pipefail
function docker-build() {
local target="${TARGET:-}"
local image="codercom/nbin-${target}"
local token="${GITHUB_TOKEN:-}"
local minify="${MINIFY:-}"
if [[ "${target}" == "linux" ]] ; then
image="codercom/nbin-centos"
fi
local containerId
# Use a mount so we can cache the results.
containerId=$(docker create --network=host --rm -it -v "$(pwd)":/src "${image}")
docker start "${containerId}"
# TODO: Might be better to move these dependencies to the images or create new
# ones on top of these.
if [[ "${image}" == "codercom/nbin-alpine" ]] ; then
docker exec "${containerId}" apk add libxkbfile-dev libsecret-dev
else
docker exec "${containerId}" yum install -y libxkbfile-devel libsecret-devel git
fi
function docker-exec() {
local command="${1}" ; shift
local args="'${vscodeVersion}' '${codeServerVersion}'"
docker exec "${containerId}" \
bash -c "cd /src && CI=true GITHUB_TOKEN=${token} MINIFY=${minify} yarn ${command} ${args}"
}
docker-exec build
if [[ -n "${package}" ]] ; then
docker-exec binary
docker-exec package
fi
docker kill "${containerId}"
}
function local-build() {
function local-exec() {
local command="${1}" ; shift
CI=true yarn "${command}" "${vscodeVersion}" "${codeServerVersion}"
}
local-exec build
if [[ -n "${package}" ]] ; then
local-exec binary
local-exec package
fi
}
# Build code-server in the CI.
function main() { function main() {
cd "$(dirname "${0}")/.." cd "$(dirname "${0}")/.."
local codeServerVersion="${VERSION:-}" # Get the version information. If a specific version wasn't set, generate it
local vscodeVersion="${VSCODE_VERSION:-}" # from the tag and VS Code version.
local ostype="${OSTYPE:-}" local vscode_version=${VSCODE_VERSION:-1.41.1}
local package="${PACKAGE:-}" local code_server_version=${VERSION:-${TRAVIS_TAG:-${DRONE_TAG:-daily}}}
if [[ -z "${codeServerVersion}" ]] ; then # Remove everything that isn't the current VS Code source for caching
>&2 echo "Must set VERSION environment variable"; exit 1 # (otherwise the cache will contain old versions).
if [[ -d "source/vscode-$vscode_version-source" ]] ; then
mv "source/vscode-$vscode_version-source" "vscode-$vscode_version-source"
fi
rm -rf source/vscode-*-source
if [[ -d "vscode-$vscode_version-source" ]] ; then
mv "vscode-$vscode_version-source" "source/vscode-$vscode_version-source"
fi fi
if [[ -z "${vscodeVersion}" ]] ; then YARN_CACHE_FOLDER="$(pwd)/yarn-cache"
>&2 echo "Must set VSCODE_VERSION environment variable"; exit 1 export YARN_CACHE_FOLDER
# Always minify and package on tags since that's when releases are pushed.
if [[ -n ${DRONE_TAG:-} || -n ${TRAVIS_TAG:-} ]] ; then
export MINIFY="true"
export PACKAGE="true"
fi fi
if [[ "${ostype}" == "darwin"* ]]; then function run-yarn() {
local-build yarn "$1" "$vscode_version" "$code_server_version"
else }
docker-build
run-yarn build
run-yarn binary
if [[ -n ${PACKAGE:-} ]] ; then
run-yarn package
fi
# In this case provide a plainly named "code-server" binary.
if [[ -n ${BINARY:-} ]] ; then
mv binaries/code-server*-vsc* binaries/code-server
fi fi
} }

View File

@@ -1,4 +1,4 @@
# We deploy with ubuntu so that devs have a familiar environment. # We deploy with Ubuntu so that devs have a familiar environment.
FROM ubuntu:18.04 FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
@@ -22,9 +22,10 @@ RUN adduser --gecos '' --disabled-password coder && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
USER coder USER coder
# We create first instead of just using WORKDIR as when WORKDIR creates, the # Create first so these directories will be owned by coder instead of root
# user is root. # (workdir and mounting appear to both default to root).
RUN mkdir -p /home/coder/project RUN mkdir -p /home/coder/project
RUN mkdir -p /home/coder/.local/share/code-server
WORKDIR /home/coder/project WORKDIR /home/coder/project

19
scripts/test.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env sh
# test.sh -- Simple test for CI.
# We'll have more involved tests eventually. This just ensures the binary has
# been built and runs.
set -eu
main() {
cd "$(dirname "$0")/.."
version=$(./binaries/code-server* --version | head -1)
echo "Got '$version' for the version"
case $version in
*-vsc1.41.1) exit 0 ;;
*) exit 1 ;;
esac
}
main "$@"

View File

@@ -1,42 +1,37 @@
diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts
index 6d41e85e42..f845d0bf9e 100644 index 231180d513..5b98e191c1 100644
--- a/src/vs/base/common/network.ts --- a/src/vs/base/common/network.ts
+++ b/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts
@@ -48,7 +48,9 @@ export namespace Schemas { @@ -88,16 +88,17 @@ class RemoteAuthoritiesImpl {
export const command: string = 'command';
- export const vscodeRemote: string = 'vscode-remote';
+ // NOTE@coder: Changed this so it'll be reflected in the explorer to prevent
+ // confusion with vscode-remote itself.
+ export const vscodeRemote: string = 'code-server';
export const vscodeRemoteResource: string = 'vscode-remote-resource';
@@ -96,12 +98,12 @@ class RemoteAuthoritiesImpl {
if (host && host.indexOf(':') !== -1) { if (host && host.indexOf(':') !== -1) {
host = `[${host}]`; host = `[${host}]`;
} }
- const port = this._ports[authority]; - const port = this._ports[authority];
+ // NOTE@coder: Changed this to work against the current path. + // const port = this._ports[authority];
const connectionToken = this._connectionTokens[authority]; const connectionToken = this._connectionTokens[authority];
let query = `path=${encodeURIComponent(uri.path)}`;
if (typeof connectionToken === 'string') {
query += `&tkn=${encodeURIComponent(connectionToken)}`;
}
+ // NOTE@coder: Changed this to work against the current path.
return URI.from({ return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
- authority: `${host}:${port}`, - authority: `${host}:${port}`,
- path: `/vscode-remote-resource`, - path: `/vscode-remote-resource`,
+ authority: window.location.host, + authority: window.location.host,
+ path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`, + path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`,
query: `path=${encodeURIComponent(uri.path)}&tkn=${encodeURIComponent(connectionToken)}` query
}); });
} }
diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts
index a657f4a4d9..66bd13dffa 100644 index 5a631e0b39..8a2b1518d6 100644
--- a/src/vs/base/common/platform.ts --- a/src/vs/base/common/platform.ts
+++ b/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts
@@ -56,6 +56,16 @@ if (typeof navigator === 'object' && !isElectronRenderer) { @@ -59,6 +59,17 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
_isWeb = true; _isWeb = true;
_locale = navigator.language; _locale = navigator.language;
_language = _locale; _language = _locale;
+ // NOTE@coder: make languages work.
+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration'); + const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
+ const rawNlsConfig = el && el.getAttribute('data-settings'); + const rawNlsConfig = el && el.getAttribute('data-settings');
+ if (rawNlsConfig) { + if (rawNlsConfig) {
@@ -50,11 +45,27 @@ index a657f4a4d9..66bd13dffa 100644
} else if (typeof process === 'object') { } else if (typeof process === 'object') {
_isWindows = (process.platform === 'win32'); _isWindows = (process.platform === 'win32');
_isMacintosh = (process.platform === 'darwin'); _isMacintosh = (process.platform === 'darwin');
diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts
index c52f7b3774..5a7e7f579e 100644
--- a/src/vs/base/common/processes.ts
+++ b/src/vs/base/common/processes.ts
@@ -110,7 +110,10 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
/^ELECTRON_.+$/,
/^GOOGLE_API_KEY$/,
/^VSCODE_.+$/,
- /^SNAP(|_.*)$/
+ /^SNAP(|_.*)$/,
+ // NOTE@coder: add our own environment variables.
+ /^NBIN_BYPASS$/,
+ /^LAUNCH_VSCODE$/
];
const envKeys = Object.keys(env);
envKeys
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index 3ae24454cb..fac8679290 100644 index 2c64061da7..c0ef8faedd 100644
--- a/src/vs/base/node/languagePacks.js --- a/src/vs/base/node/languagePacks.js
+++ b/src/vs/base/node/languagePacks.js +++ b/src/vs/base/node/languagePacks.js
@@ -146,7 +146,10 @@ function factory(nodeRequire, path, fs, perf) { @@ -128,7 +128,10 @@ function factory(nodeRequire, path, fs, perf) {
function getLanguagePackConfigurations(userDataPath) { function getLanguagePackConfigurations(userDataPath) {
const configFile = path.join(userDataPath, 'languagepacks.json'); const configFile = path.join(userDataPath, 'languagepacks.json');
try { try {
@@ -67,10 +78,10 @@ index 3ae24454cb..fac8679290 100644
// Do nothing. If we can't read the file we have no // Do nothing. If we can't read the file we have no
// language pack config. // language pack config.
diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts
index 990755c4f3..06449bb9cb 100644 index 033cdc575f..23f775f27d 100644
--- a/src/vs/platform/environment/common/environment.ts --- a/src/vs/platform/environment/common/environment.ts
+++ b/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts
@@ -36,6 +36,8 @@ export interface ParsedArgs { @@ -37,6 +37,8 @@ export interface ParsedArgs {
logExtensionHostCommunication?: boolean; logExtensionHostCommunication?: boolean;
'extensions-dir'?: string; 'extensions-dir'?: string;
'builtin-extensions-dir'?: string; 'builtin-extensions-dir'?: string;
@@ -79,18 +90,20 @@ index 990755c4f3..06449bb9cb 100644
extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
extensionTestsPath?: string; // either a local path or a URI extensionTestsPath?: string; // either a local path or a URI
'extension-development-confirm-save'?: boolean; 'extension-development-confirm-save'?: boolean;
@@ -169,4 +171,6 @@ export interface IEnvironmentService { @@ -144,6 +146,8 @@ export interface IEnvironmentService extends IUserHomeProvider {
driverVerbose: boolean; disableExtensions: boolean | string[];
builtinExtensionsPath: string;
galleryMachineIdResource?: URI; extensionsPath?: string;
+ extraExtensionPaths: string[]; + extraExtensionPaths: string[];
+ extraBuiltinExtensionPaths: string[]; + extraBuiltinExtensionPaths: string[];
} extensionDevelopmentLocationURI?: URI[];
extensionTestsLocationURI?: URI;
logExtensionHostCommunication?: boolean;
diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts
index 3e48fe4ddd..2212ff5471 100644 index 6832b93c5c..1e451584eb 100644
--- a/src/vs/platform/environment/node/argv.ts --- a/src/vs/platform/environment/node/argv.ts
+++ b/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts
@@ -58,6 +58,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = { @@ -55,6 +55,8 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
'builtin-extensions-dir': { type: 'string' }, 'builtin-extensions-dir': { type: 'string' },
@@ -99,20 +112,16 @@ index 3e48fe4ddd..2212ff5471 100644
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") }, 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") }, 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
@@ -185,7 +187,7 @@ export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, err @@ -308,4 +310,3 @@ export function buildHelpMessage(productName: string, executableName: string, ve
delete parsedArgs[o.deprecates]; export function buildVersionMessage(version: string | undefined, commit: string | undefined): string {
} return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`;
}
- if (val) { -
+ if (typeof val !== 'undefined') {
if (o.type === 'string[]') {
if (val && !Array.isArray(val)) {
val = [val];
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
index f7d207009d..5c37b52dab 100644 index 99cab4bba2..531b1d7177 100644
--- a/src/vs/platform/environment/node/environmentService.ts --- a/src/vs/platform/environment/node/environmentService.ts
+++ b/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts
@@ -260,6 +260,12 @@ export class EnvironmentService implements IEnvironmentService { @@ -266,6 +266,12 @@ export class EnvironmentService implements IEnvironmentService {
get driverHandle(): string | undefined { return this._args['driver']; } get driverHandle(): string | undefined { return this._args['driver']; }
get driverVerbose(): boolean { return !!this._args['driver-verbose']; } get driverVerbose(): boolean { return !!this._args['driver-verbose']; }
@@ -126,10 +135,10 @@ index f7d207009d..5c37b52dab 100644
constructor(private _args: ParsedArgs, private _execPath: string) { constructor(private _args: ParsedArgs, private _execPath: string) {
if (!process.env['VSCODE_LOGS']) { if (!process.env['VSCODE_LOGS']) {
diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
index f0eaa74a59..3abf9e1752 100644 index 5bfc2bb66c..49a6ce8540 100644
--- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
@@ -731,11 +731,15 @@ export class ExtensionManagementService extends Disposable implements IExtension @@ -741,11 +741,15 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanSystemExtensions(): Promise<ILocalExtension[]> { private scanSystemExtensions(): Promise<ILocalExtension[]> {
this.logService.trace('Started scanning system extensions'); this.logService.trace('Started scanning system extensions');
@@ -150,7 +159,7 @@ index f0eaa74a59..3abf9e1752 100644
if (this.environmentService.isBuilt) { if (this.environmentService.isBuilt) {
return systemExtensionsPromise; return systemExtensionsPromise;
} }
@@ -757,9 +761,16 @@ export class ExtensionManagementService extends Disposable implements IExtension @@ -767,9 +771,16 @@ export class ExtensionManagementService extends Disposable implements IExtension
.then(([systemExtensions, devSystemExtensions]) => [...systemExtensions, ...devSystemExtensions]); .then(([systemExtensions, devSystemExtensions]) => [...systemExtensions, ...devSystemExtensions]);
} }
@@ -168,7 +177,7 @@ index f0eaa74a59..3abf9e1752 100644
.then(([uninstalled, extensions]) => { .then(([uninstalled, extensions]) => {
extensions = extensions.filter(e => !uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]); extensions = extensions.filter(e => !uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]);
if (excludeOutdated) { if (excludeOutdated) {
@@ -774,6 +785,12 @@ export class ExtensionManagementService extends Disposable implements IExtension @@ -784,6 +795,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanExtensions(root: string, type: ExtensionType): Promise<ILocalExtension[]> { private scanExtensions(root: string, type: ExtensionType): Promise<ILocalExtension[]> {
const limiter = new Limiter<any>(10); const limiter = new Limiter<any>(10);
return pfs.readdir(root) return pfs.readdir(root)
@@ -181,7 +190,7 @@ index f0eaa74a59..3abf9e1752 100644
.then(extensionsFolders => Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type))))) .then(extensionsFolders => Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
.then(extensions => extensions.filter(e => e && e.identifier)); .then(extensions => extensions.filter(e => e && e.identifier));
} }
@@ -812,7 +829,7 @@ export class ExtensionManagementService extends Disposable implements IExtension @@ -822,7 +839,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
private async removeUninstalledExtensions(): Promise<void> { private async removeUninstalledExtensions(): Promise<void> {
const uninstalled = await this.getUninstalledExtensions(); const uninstalled = await this.getUninstalledExtensions();
@@ -190,7 +199,7 @@ index f0eaa74a59..3abf9e1752 100644
const installed: Set<string> = new Set<string>(); const installed: Set<string> = new Set<string>();
for (const e of extensions) { for (const e of extensions) {
if (!uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]) { if (!uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]) {
@@ -831,7 +848,7 @@ export class ExtensionManagementService extends Disposable implements IExtension @@ -841,7 +858,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
} }
private removeOutdatedExtensions(): Promise<void> { private removeOutdatedExtensions(): Promise<void> {
@@ -200,18 +209,21 @@ index f0eaa74a59..3abf9e1752 100644
const toRemove: ILocalExtension[] = []; const toRemove: ILocalExtension[] = [];
diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts
index 2de51e8d32..837770990e 100644 index 804d113856..4b651e5c77 100644
--- a/src/vs/platform/product/common/product.ts --- a/src/vs/platform/product/common/product.ts
+++ b/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts
@@ -22,10 +22,16 @@ if (isWeb) { @@ -22,11 +22,19 @@ if (isWeb) {
if (Object.keys(product).length === 0) { if (Object.keys(product).length === 0) {
assign(product, { assign(product, {
version: '1.39.0-dev', version: '1.41.0-dev',
+ codeServerVersion: 'dev', + codeServerVersion: 'dev',
nameLong: 'Visual Studio Code Web Dev', nameLong: 'Visual Studio Code Web Dev',
nameShort: 'VSCode Web Dev' nameShort: 'VSCode Web Dev',
urlProtocol: 'code-oss'
}); });
} }
+
+ // NOTE@coder: enable injecting settings from the server.
+ const el = document.getElementById('vscode-remote-product-configuration'); + const el = document.getElementById('vscode-remote-product-configuration');
+ const rawProductConfiguration = el && el.getAttribute('data-settings'); + const rawProductConfiguration = el && el.getAttribute('data-settings');
+ if (rawProductConfiguration) { + if (rawProductConfiguration) {
@@ -220,7 +232,7 @@ index 2de51e8d32..837770990e 100644
} }
// Node: AMD loader // Node: AMD loader
@@ -35,7 +41,7 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === ' @@ -36,7 +44,7 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === '
const rootPath = path.dirname(getPathFromAmdModule(require, '')); const rootPath = path.dirname(getPathFromAmdModule(require, ''));
product = assign({}, require.__$__nodeRequire(path.join(rootPath, 'product.json')) as IProductConfiguration); product = assign({}, require.__$__nodeRequire(path.join(rootPath, 'product.json')) as IProductConfiguration);
@@ -229,7 +241,7 @@ index 2de51e8d32..837770990e 100644
// Running out of sources // Running out of sources
if (env['VSCODE_DEV']) { if (env['VSCODE_DEV']) {
@@ -47,7 +53,8 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === ' @@ -48,7 +56,8 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === '
} }
assign(product, { assign(product, {
@@ -240,10 +252,10 @@ index 2de51e8d32..837770990e 100644
} }
diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts
index 5aa5c32d7e..e4e7fd4174 100644 index 6db9725704..779b3cbdea 100644
--- a/src/vs/platform/product/common/productService.ts --- a/src/vs/platform/product/common/productService.ts
+++ b/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts
@@ -15,6 +15,7 @@ export interface IProductService extends Readonly<IProductConfiguration> { @@ -16,6 +16,7 @@ export interface IProductService extends Readonly<IProductConfiguration> {
export interface IProductConfiguration { export interface IProductConfiguration {
readonly version: string; readonly version: string;
@@ -272,6 +284,59 @@ index d0f6e6b18a..1966fd297d 100644
- -
- -
- -
diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts
index 81ec255e65..c94829fc6a 100644
--- a/src/vs/platform/request/common/request.ts
+++ b/src/vs/platform/request/common/request.ts
@@ -16,7 +16,7 @@ export const IRequestService = createDecorator<IRequestService>('requestService'
export interface IRequestService {
_serviceBrand: undefined;
- request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext>;
+ request(options: IRequestOptions, token: CancellationToken, gzip?: boolean): Promise<IRequestContext>;
resolveProxy(url: string): Promise<string | undefined>;
}
diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts
index ad44dcbc33..7a7b5261ff 100644
--- a/src/vs/platform/request/node/requestService.ts
+++ b/src/vs/platform/request/node/requestService.ts
@@ -57,7 +57,7 @@ export class RequestService extends Disposable implements IRequestService {
this.authorization = config.http && config.http.proxyAuthorization;
}
- async request(options: NodeRequestOptions, token: CancellationToken): Promise<IRequestContext> {
+ async request(options: NodeRequestOptions, token: CancellationToken, gzip?: boolean): Promise<IRequestContext> {
this.logService.trace('RequestService#request', options.url);
const { proxyUrl, strictSSL } = this;
@@ -70,7 +70,7 @@ export class RequestService extends Disposable implements IRequestService {
options.headers = assign(options.headers || {}, { 'Proxy-Authorization': this.authorization });
}
- return this._request(options, token);
+ return this._request(options, token, gzip);
}
private async getNodeRequest(options: IRequestOptions): Promise<IRawRequestFunction> {
@@ -79,7 +79,7 @@ export class RequestService extends Disposable implements IRequestService {
return module.request;
}
- private _request(options: NodeRequestOptions, token: CancellationToken): Promise<IRequestContext> {
+ private _request(options: NodeRequestOptions, token: CancellationToken, gzip?: boolean): Promise<IRequestContext> {
return new Promise<IRequestContext>(async (c, e) => {
let req: http.ClientRequest;
@@ -114,7 +114,7 @@ export class RequestService extends Disposable implements IRequestService {
} else {
let stream: streams.ReadableStream<Uint8Array> = res;
- if (res.headers['content-encoding'] === 'gzip') {
+ if (gzip || res.headers['content-encoding'] === 'gzip') {
stream = res.pipe(createGunzip());
}
diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts
index 8a1c95d37b..8225a85d47 100644 index 8a1c95d37b..8225a85d47 100644
--- a/src/vs/platform/update/electron-main/abstractUpdateService.ts --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts
@@ -322,7 +387,7 @@ index 2905c52411..303ddf211f 100644
export class ExtensionPoints implements IWorkbenchContribution { export class ExtensionPoints implements IWorkbenchContribution {
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index 230d09a290..84753e6ac7 100644 index ea5ad7991f..8d8e99339e 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts --- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log'; @@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log';
@@ -350,10 +415,10 @@ index 230d09a290..84753e6ac7 100644
// automatically create and register addressable instances // automatically create and register addressable instances
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations)); const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 7bd6aa6cb7..7c28136366 100644 index 3dab81c9c5..73fc57118a 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts --- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -627,6 +627,10 @@ export interface MainThreadLabelServiceShape extends IDisposable { @@ -655,6 +655,10 @@ export interface MainThreadLabelServiceShape extends IDisposable {
$unregisterResourceLabelFormatter(handle: number): void; $unregisterResourceLabelFormatter(handle: number): void;
} }
@@ -364,7 +429,7 @@ index 7bd6aa6cb7..7c28136366 100644
export interface MainThreadSearchShape extends IDisposable { export interface MainThreadSearchShape extends IDisposable {
$registerFileSearchProvider(handle: number, scheme: string): void; $registerFileSearchProvider(handle: number, scheme: string): void;
$registerTextSearchProvider(handle: number, scheme: string): void; $registerTextSearchProvider(handle: number, scheme: string): void;
@@ -860,6 +864,13 @@ export interface ExtHostLabelServiceShape { @@ -888,6 +892,13 @@ export interface ExtHostLabelServiceShape {
$registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable; $registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable;
} }
@@ -378,7 +443,7 @@ index 7bd6aa6cb7..7c28136366 100644
export interface ExtHostSearchShape { export interface ExtHostSearchShape {
$provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise<search.ISearchCompleteStats>; $provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise<search.ISearchCompleteStats>;
$provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise<search.ISearchCompleteStats>; $provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise<search.ISearchCompleteStats>;
@@ -1384,7 +1395,8 @@ export const MainContext = { @@ -1431,7 +1442,8 @@ export const MainContext = {
MainThreadSearch: createMainId<MainThreadSearchShape>('MainThreadSearch'), MainThreadSearch: createMainId<MainThreadSearchShape>('MainThreadSearch'),
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'), MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'), MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
@@ -388,7 +453,7 @@ index 7bd6aa6cb7..7c28136366 100644
}; };
export const ExtHostContext = { export const ExtHostContext = {
@@ -1418,5 +1430,6 @@ export const ExtHostContext = { @@ -1465,5 +1477,6 @@ export const ExtHostContext = {
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'), ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'), ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'), ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
@@ -397,7 +462,7 @@ index 7bd6aa6cb7..7c28136366 100644
+ ExtHostNodeProxy: createMainId<ExtHostNodeProxyShape>('ExtHostNodeProxy') + ExtHostNodeProxy: createMainId<ExtHostNodeProxyShape>('ExtHostNodeProxy')
}; };
diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts
index b5ce835e07..22be8516c1 100644 index a3b5ed0057..f47a97336d 100644
--- a/src/vs/workbench/api/common/extHostExtensionService.ts --- a/src/vs/workbench/api/common/extHostExtensionService.ts
+++ b/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
@@ -443,7 +508,7 @@ index b5ce835e07..22be8516c1 100644
this._disposables = new DisposableStore(); this._disposables = new DisposableStore();
this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace);
@@ -331,14 +335,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio @@ -337,14 +341,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
return Promise.all([ return Promise.all([
@@ -461,7 +526,7 @@ index b5ce835e07..22be8516c1 100644
private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<vscode.ExtensionContext> { private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<vscode.ExtensionContext> {
diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts
index a227d8a67b..92553a976c 100644 index 9ae085f536..4e3ccca3d3 100644
--- a/src/vs/workbench/api/node/extHost.services.ts --- a/src/vs/workbench/api/node/extHost.services.ts
+++ b/src/vs/workbench/api/node/extHost.services.ts +++ b/src/vs/workbench/api/node/extHost.services.ts
@@ -26,6 +26,8 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS @@ -26,6 +26,8 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS
@@ -473,7 +538,7 @@ index a227d8a67b..92553a976c 100644
// register singleton services // register singleton services
registerSingleton(ILogService, ExtHostLogService); registerSingleton(ILogService, ExtHostLogService);
@@ -42,3 +44,19 @@ registerSingleton(IExtHostSearch, ExtHostSearch); @@ -42,3 +44,19 @@ registerSingleton(IExtHostSearch, NativeExtHostSearch);
registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
registerSingleton(IExtHostExtensionService, ExtHostExtensionService); registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(IExtHostStorage, ExtHostStorage); registerSingleton(IExtHostStorage, ExtHostStorage);
@@ -494,7 +559,7 @@ index a227d8a67b..92553a976c 100644
+} +}
+registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy(IExtHostNodeProxy) {}); +registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy(IExtHostNodeProxy) {});
diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts
index 49a8e254fd..99d233aed5 100644 index a1c3e50ffd..910627aaf9 100644
--- a/src/vs/workbench/api/node/extHostExtensionService.ts --- a/src/vs/workbench/api/node/extHostExtensionService.ts
+++ b/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -13,6 +13,8 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer @@ -13,6 +13,8 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer
@@ -506,7 +571,7 @@ index 49a8e254fd..99d233aed5 100644
class NodeModuleRequireInterceptor extends RequireInterceptor { class NodeModuleRequireInterceptor extends RequireInterceptor {
@@ -75,7 +77,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { @@ -76,7 +78,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
}; };
} }
@@ -518,8 +583,72 @@ index 49a8e254fd..99d233aed5 100644
if (module.scheme !== Schemas.file) { if (module.scheme !== Schemas.file) {
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`); throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
} }
diff --git a/src/vs/workbench/api/node/extHostStoragePaths.ts b/src/vs/workbench/api/node/extHostStoragePaths.ts
index afdd6bf398..ac91318ce3 100644
--- a/src/vs/workbench/api/node/extHostStoragePaths.ts
+++ b/src/vs/workbench/api/node/extHostStoragePaths.ts
@@ -5,13 +5,14 @@
import * as path from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
-import * as pfs from 'vs/base/node/pfs';
-import { IEnvironment, IStaticWorkspaceData } from 'vs/workbench/api/common/extHost.protocol';
+import { IEnvironment, IStaticWorkspaceData, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { ILogService } from 'vs/platform/log/common/log';
+import { IExtHostRpcService } from '../common/extHostRpcService';
+import { VSBuffer } from 'vs/base/common/buffer';
export class ExtensionStoragePaths implements IExtensionStoragePaths {
@@ -26,6 +27,7 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths {
constructor(
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService private readonly _logService: ILogService,
+ @IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService,
) {
this._workspace = withNullAsUndefined(initData.workspace);
this._environment = initData.environment;
@@ -54,21 +56,25 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths {
const storageName = this._workspace.id;
const storagePath = path.join(this._environment.appSettingsHome.fsPath, 'workspaceStorage', storageName);
- const exists = await pfs.dirExists(storagePath);
+ // NOTE@coder: Use the file system proxy so this will work in the browser.
+ // writeFile performs a mkdirp so we don't need to bother ourselves.
+ const fileSystem = this._extHostRpc.getProxy(MainContext.MainThreadFileSystem);
+ const exists = fileSystem.$stat(URI.file(storagePath))
if (exists) {
return storagePath;
}
try {
- await pfs.mkdirp(storagePath);
- await pfs.writeFile(
- path.join(storagePath, 'meta.json'),
- JSON.stringify({
- id: this._workspace.id,
- configuration: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(),
- name: this._workspace.name
- }, undefined, 2)
+ await fileSystem.$writeFile(
+ URI.file(path.join(storagePath, 'meta.json')),
+ VSBuffer.fromString(
+ JSON.stringify({
+ id: this._workspace.id,
+ configuration: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(),
+ name: this._workspace.name
+ }, undefined, 2)
+ )
);
return storagePath;
diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts
index afd82468c0..289145be54 100644 index 4781f22676..25143a97c0 100644
--- a/src/vs/workbench/api/worker/extHostExtensionService.ts --- a/src/vs/workbench/api/worker/extHostExtensionService.ts
+++ b/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts
@@ -9,6 +9,9 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost @@ -9,6 +9,9 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost
@@ -539,7 +668,7 @@ index afd82468c0..289145be54 100644
- protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> { - protected async _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ protected async _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> { + protected async _loadCommonJSModule<T>(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
+ if (!URI.isUri(module) && module.extensionKind !== 'web') { + if (!URI.isUri(module) && module.extensionKind !== 'web') {
+ return loadCommonJSModule(module, activationTimesBuilder, this._nodeProxy, this._logService, this._fakeModules.getModule('vscode', module.extensionLocation)); + return loadCommonJSModule(module, activationTimesBuilder, this._nodeProxy, this._logService, this._fakeModules!.getModule('vscode', module.extensionLocation));
+ } + }
+ +
+ if (!URI.isUri(module)) { + if (!URI.isUri(module)) {
@@ -552,54 +681,24 @@ index afd82468c0..289145be54 100644
const _exports = {}; const _exports = {};
const _module = { exports: _exports }; const _module = { exports: _exports };
const _require = (request: string) => { const _require = (request: string) => {
- const result = this._fakeModules.getModule(request, module); - const result = this._fakeModules!.getModule(request, module);
+ const result = this._fakeModules.getModule(request, <URI>module); + const result = this._fakeModules!.getModule(request, <URI>module);
if (result === undefined) { if (result === undefined) {
throw new Error(`Cannot load module '${request}'`); throw new Error(`Cannot load module '${request}'`);
} }
diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts
index 005a025aa9..ab392630c0 100644
--- a/src/vs/workbench/browser/dnd.ts
+++ b/src/vs/workbench/browser/dnd.ts
@@ -32,6 +32,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
import { withNullAsUndefined } from 'vs/base/common/types';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { IUploadService } from 'vs/server/src/browser/upload';
export interface IDraggedResource {
resource: URI;
@@ -167,14 +168,15 @@ export class ResourcesDropHandler {
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
- @IHostService private readonly hostService: IHostService
+ @IHostService private readonly hostService: IHostService,
+ @IUploadService private readonly uploadService: IUploadService,
) {
}
async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled);
if (!untitledOrFileResources.length) {
- return;
+ return this.uploadService.handleDrop(event, resolveTargetGroup, afterDrop, targetIndex);
}
// Make the window active to handle the drop properly within
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
index 84c46faa36..957e8412e1 100644 index 807ac56d8f..a22bd92a82 100644
--- a/src/vs/workbench/browser/web.main.ts --- a/src/vs/workbench/browser/web.main.ts
+++ b/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts
@@ -48,6 +48,7 @@ import { toLocalISOString } from 'vs/base/common/date'; @@ -50,6 +50,7 @@ import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedD
import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider';
import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider'; import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider';
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
+import { initialize } from 'vs/server/src/browser/client'; +import { initialize } from 'vs/server/src/browser/client';
class BrowserMain extends Disposable { class BrowserMain extends Disposable {
@@ -84,6 +85,7 @@ class BrowserMain extends Disposable { @@ -86,6 +87,7 @@ class BrowserMain extends Disposable {
// Startup // Startup
workbench.startup(); workbench.startup();
@@ -607,130 +706,124 @@ index 84c46faa36..957e8412e1 100644
} }
private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void { private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void {
@@ -238,6 +240,7 @@ class BrowserMain extends Disposable { @@ -247,6 +249,7 @@ class BrowserMain extends Disposable {
const channel = connection.getChannel<IChannel>(REMOTE_FILE_SYSTEM_CHANNEL_NAME); const channel = connection.getChannel<IChannel>(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(channel, remoteAgentService.getEnvironment()));
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
+ fileService.registerProvider(Schemas.file, remoteFileSystemProvider); + fileService.registerProvider(Schemas.file, remoteFileSystemProvider);
if (!this.configuration.userDataProvider) { if (!this.configuration.userDataProvider) {
const remoteUserDataUri = this.getRemoteUserDataUri(); const remoteUserDataUri = this.getRemoteUserDataUri();
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts
index 1f4cd95f65..061931cbde 100644 index c509716fc4..e416413084 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts --- a/src/vs/workbench/common/resources.ts
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/common/resources.ts
@@ -209,7 +209,7 @@ configurationRegistry.registerConfiguration({ @@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
'files.exclude': { import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
'type': 'object', import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), import { withNullAsUndefined } from 'vs/base/common/types';
- 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true }, +import { Schemas } from 'vs/base/common/network';
+ 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true, '**/.code-server-partial-upload-*': true },
'scope': ConfigurationScope.RESOURCE,
'additionalProperties': {
'anyOf': [
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
index cc4bcb28c5..98679a8b32 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
@@ -47,6 +47,7 @@ import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/work
import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { Emitter } from 'vs/base/common/event';
+import { IUploadService } from 'vs/server/src/browser/upload';
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> { export class ResourceContextKey extends Disposable implements IContextKey<URI> {
@@ -444,7 +445,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> { @@ -63,7 +64,8 @@ export class ResourceContextKey extends Disposable implements IContextKey<URI> {
@IInstantiationService private instantiationService: IInstantiationService, set(value: URI | null) {
@ITextFileService private textFileService: ITextFileService, if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
@IHostService private hostService: IHostService, this._resourceKey.set(value);
- @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService - this._schemeKey.set(value ? value.scheme : null);
+ @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, + // NOTE@coder: fixes extensions matching against file schemas.
+ @IUploadService private readonly uploadService: IUploadService, + this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null);
) { this._filenameKey.set(value ? basename(value) : null);
this.toDispose = []; this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
this._extensionKey.set(value ? extname(value) : null);
@@ -605,6 +607,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}
private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
+ return this.uploadService.handleExternalDrop(data, target, originalEvent);
const droppedResources = extractResources(originalEvent, true);
// Check for dropped external files to be folders
const result = await this.fileService.resolveAll(droppedResources);
diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js
index e6b9fd854b..a3d0a46e3a 100644 index 138707c9a9..9134d5f503 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/main.js --- a/src/vs/workbench/contrib/webview/browser/pre/main.js
+++ b/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js
@@ -308,7 +308,8 @@ @@ -329,7 +329,8 @@
} else {
// Rewrite vscode-resource in csp
if (data.endpoint) { if (data.endpoint) {
- csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint)); try {
+ // NOTE@coder: Add back the trailing slash so it'll work for sub-paths. const endpointUrl = new URL(data.endpoint);
+ csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint + "/")); - csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:(?=(\s|;|$))/g, endpointUrl.origin));
} + // NOTE@coder: Add back the trailing slash so it'll work for sub-paths.
} + csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:(?=(\s|;|$))/g, endpointUrl.origin + "/"));
} catch (e) {
console.error('Could not rewrite csp');
}
diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts
index f67f9aa064..add754cd5a 100644
--- a/src/vs/workbench/services/dialogs/browser/dialogService.ts
+++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts
@@ -122,11 +122,12 @@ export class DialogService implements IDialogService {
async about(): Promise<void> {
const detail = nls.localize('aboutDetail',
- "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
+ "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}\nCode Server Version: {4}",
this.productService.version || 'Unknown',
this.productService.commit || 'Unknown',
this.productService.date || 'Unknown',
- navigator.userAgent
+ navigator.userAgent,
+ this.productService.codeServerVersion || 'Unknown',
);
const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail, cancelId: 1 });
diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts
index 5f221e07ff..bfd592382c 100644 index d54e68fa70..b2c4ea5f6a 100644
--- a/src/vs/workbench/services/environment/browser/environmentService.ts --- a/src/vs/workbench/services/environment/browser/environmentService.ts
+++ b/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -15,7 +15,6 @@ import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/window @@ -189,8 +189,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api';
-import product from 'vs/platform/product/common/product';
export class BrowserWindowConfiguration implements IWindowConfiguration {
@@ -180,12 +179,13 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
driverHandle?: string;
driverVerbose: boolean;
galleryMachineIdResource?: URI;
+ extraExtensionPaths: string[];
+ extraBuiltinExtensionPaths: string[];
readonly logFile: URI;
@memoize
get webviewExternalEndpoint(): string { get webviewExternalEndpoint(): string {
- // TODO: get fallback from product.json - // TODO: get fallback from product.json
- return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}') - return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}').replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37');
- .replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44');
+ // NOTE@coder: Modified to work against the current URL. + // NOTE@coder: Modified to work against the current URL.
+ return `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview/`; + return `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview/`;
} }
get webviewResourceRoot(): string { @memoize
@@ -267,6 +267,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
//#region TODO ENABLE IN WEB
galleryMachineIdResource?: URI;
+ extraExtensionPaths!: string[];
+ extraBuiltinExtensionPaths!: string[];
//#endregion
diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts
index 000e5f7b4a..39f46e68a1 100644 index d164f2c127..5a08106f04 100644
--- a/src/vs/workbench/services/extensions/browser/extensionService.ts --- a/src/vs/workbench/services/extensions/browser/extensionService.ts
+++ b/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts
@@ -119,6 +119,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten @@ -119,6 +119,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
} else { } else {
// remote: only enabled and none-web'ish extension // remote: only enabled and none-web'ish extension
+ localExtensions.push(...remoteEnv.extensions.filter(extension => this._isEnabled(extension) && isWebExtension(extension, this._configService))); + localExtensions.push(...remoteEnv.extensions.filter(extension => this._isEnabled(extension) && canExecuteOnWeb(extension, this._productService, this._configService)));
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !isWebExtension(extension, this._configService)); remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !canExecuteOnWeb(extension, this._productService, this._configService));
this._checkEnableProposedApi(remoteEnv.extensions); this._checkEnableProposedApi(remoteEnv.extensions);
diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
index 49b2d270c0..45200ccdbb 100644 index 75f715cc51..1d6299309d 100644
--- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts
+++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts
@@ -12,7 +12,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; @@ -32,7 +32,8 @@ export function canExecuteOnWorkspace(manifest: IExtensionManifest, productServi
export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { export function canExecuteOnWeb(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
const extensionKind = getExtensionKind(manifest, configurationService); const extensionKind = getExtensionKind(manifest, productService, configurationService);
- return extensionKind === 'web'; - return extensionKind.some(kind => kind === 'web');
+ return extensionKind === 'web' || manifest.name === 'vim'; + // NOTE@coder: hardcode vim for now.
+ return extensionKind.some(kind => kind === 'web') || manifest.name === 'vim';
} }
export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { export function getExtensionKind(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): ExtensionKind[] {
diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
index 9f5a14f6cb..ca952f3d4d 100644 index 0f35c54431..32fff09b18 100644
--- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
+++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts
@@ -42,12 +42,13 @@ const args = minimist(process.argv.slice(2), { @@ -53,12 +53,13 @@ const args = minimist(process.argv.slice(2), {
const Module = require.__$__nodeRequire('module') as any; const Module = require.__$__nodeRequire('module') as any;
const originalLoad = Module._load; const originalLoad = Module._load;
@@ -746,7 +839,7 @@ index 9f5a14f6cb..ca952f3d4d 100644
}; };
})(); })();
@@ -120,8 +121,11 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> { @@ -131,8 +132,11 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
// Wait for rich client to reconnect // Wait for rich client to reconnect
protocol.onSocketClose(() => { protocol.onSocketClose(() => {
@@ -761,25 +854,49 @@ index 9f5a14f6cb..ca952f3d4d 100644
} }
} }
diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts
index 3bdfa1a79f..ded21cf9c6 100644 index 8a65101aa4..e9c66b3b20 100644
--- a/src/vs/workbench/services/extensions/worker/extHost.services.ts --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts
+++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts
@@ -21,6 +21,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensio @@ -18,9 +18,10 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService';
-import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
+import { ExtHostNodeProxy, IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy'; +import { ExtHostNodeProxy, IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy';
+import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths';
// register singleton services // register singleton services
registerSingleton(ILogService, ExtHostLogService); registerSingleton(ILogService, ExtHostLogService);
@@ -32,6 +33,7 @@ registerSingleton(IExtHostCommands, ExtHostCommands); @@ -33,25 +34,9 @@ registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors);
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors);
registerSingleton(IExtHostStorage, ExtHostStorage); registerSingleton(IExtHostStorage, ExtHostStorage);
registerSingleton(IExtHostExtensionService, ExtHostExtensionService); registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(IExtHostSearch, ExtHostSearch);
+registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy); +registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy);
// register services that only throw errors -// register services that only throw errors
function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } { -function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
- return <any>class {
- constructor() {
- return new Proxy({}, {
- get(target: any, prop: string | number) {
- if (target[prop]) {
- return target[prop];
- }
- throw new Error(`Not Implemented: ${name}->${String(prop)}`);
- }
- });
- }
- };
-}
registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService);
registerSingleton(IExtHostTask, WorkerExtHostTask);
registerSingleton(IExtHostDebugService, WorkerExtHostDebugService);
-registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) {
- whenReady = Promise.resolve();
-});
+registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths);
diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
index 99394090da..4891e0fece 100644 index 99394090da..4891e0fece 100644
--- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts --- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts
@@ -831,7 +948,7 @@ index b8f6558b2c..b1fe6b14fd 100644
// always set this._state as the state changes // always set this._state as the state changes
this.onStateChange(state => this._state = state); this.onStateChange(state => this._state = state);
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
index fa9c9dd7a9..688d6c1934 100644 index f424c87d92..af681c3c12 100644
--- a/src/vs/workbench/workbench.web.main.ts --- a/src/vs/workbench/workbench.web.main.ts
+++ b/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts
@@ -34,11 +34,14 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService'; @@ -34,11 +34,14 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService';
@@ -848,6 +965,6 @@ index fa9c9dd7a9..688d6c1934 100644
+// NOTE@coder: Use the electron-browser version since it already comes with a +// NOTE@coder: Use the electron-browser version since it already comes with a
+// channel which lets us actually perform updates. +// channel which lets us actually perform updates.
+import 'vs/workbench/services/update/electron-browser/updateService'; +import 'vs/workbench/services/update/electron-browser/updateService';
import 'vs/workbench/contrib/stats/browser/workspaceStatsService'; import 'vs/workbench/contrib/tags/browser/workspaceTagsService';
import 'vs/workbench/services/workspaces/browser/workspacesService'; import 'vs/workbench/services/workspaces/browser/workspacesService';
import 'vs/workbench/services/workspaces/browser/workspaceEditingService'; import 'vs/workbench/services/workspaces/browser/workspaceEditingService';

View File

@@ -21,7 +21,7 @@ import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
import { IThemeService } from "vs/platform/theme/common/themeService"; import { IThemeService } from "vs/platform/theme/common/themeService";
import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace"; import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace";
import * as extHostTypes from "vs/workbench/api/common/extHostTypes"; import * as extHostTypes from "vs/workbench/api/common/extHostTypes";
import { CustomTreeView, CustomTreeViewPanel } from "vs/workbench/browser/parts/views/customView"; import { CustomTreeView, CustomTreeViewPane } from "vs/workbench/browser/parts/views/customView";
import { ViewContainerViewlet } from "vs/workbench/browser/parts/views/viewsViewlet"; import { ViewContainerViewlet } from "vs/workbench/browser/parts/views/viewsViewlet";
import { Extensions as ViewletExtensions, ShowViewletAction, ViewletDescriptor, ViewletRegistry } from "vs/workbench/browser/viewlet"; import { Extensions as ViewletExtensions, ShowViewletAction, ViewletDescriptor, ViewletRegistry } from "vs/workbench/browser/viewlet";
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from "vs/workbench/common/actions"; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from "vs/workbench/common/actions";
@@ -120,11 +120,11 @@ export const coderApi = (serviceCollection: ServiceCollection): CoderApi => {
} }
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet( Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(
new ViewletDescriptor(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)), ViewletDescriptor.create(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)),
); );
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction( Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
new SyncActionDescriptor(OpenCustomViewletAction as any, id, localize("showViewlet", "Show {0}", containerName)), SyncActionDescriptor.create(OpenCustomViewletAction as any, id, localize("showViewlet", "Show {0}", containerName)),
"View: Show {0}", "View: Show {0}",
localize("view", "View"), localize("view", "View"),
); );
@@ -137,7 +137,7 @@ export const coderApi = (serviceCollection: ServiceCollection): CoderApi => {
Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry).registerViews([{ Registry.as<IViewsRegistry>(ViewsExtensions.ViewsRegistry).registerViews([{
id: viewId, id: viewId,
name: viewName, name: viewName,
ctorDescriptor: { ctor: CustomTreeViewPanel }, ctorDescriptor: { ctor: CustomTreeViewPane },
treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container), treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container),
}] as ITreeViewDescriptor[], container); }] as ITreeViewDescriptor[], container);
}, },
@@ -286,8 +286,8 @@ class StatusBarEntry implements vscode.StatusBarItem {
private _id: number; private _id: number;
private entry: IStatusBarEntry; private entry: IStatusBarEntry;
private visible: boolean; private visible?: boolean;
private disposed: boolean; private disposed?: boolean;
private statusId: string; private statusId: string;
private statusName: string; private statusName: string;
private accessor?: IStatusbarEntryAccessor; private accessor?: IStatusbarEntryAccessor;

View File

@@ -1,17 +1,21 @@
import { Emitter } from "vs/base/common/event"; import { Emitter } from "vs/base/common/event";
import { URI } from "vs/base/common/uri"; import { URI } from "vs/base/common/uri";
import { localize } from "vs/nls";
import { Extensions, IConfigurationRegistry } from "vs/platform/configuration/common/configurationRegistry";
import { registerSingleton } from "vs/platform/instantiation/common/extensions"; import { registerSingleton } from "vs/platform/instantiation/common/extensions";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection"; import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
import { ILocalizationsService } from "vs/platform/localizations/common/localizations"; import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService"; import { INotificationService, Severity } from "vs/platform/notification/common/notification";
import { Registry } from "vs/platform/registry/common/platform";
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry"; import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
import { coderApi, vscodeApi } from "vs/server/src/browser/api"; import { coderApi, vscodeApi } from "vs/server/src/browser/api";
import { IUploadService, UploadService } from "vs/server/src/browser/upload";
import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy"; import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy";
import { TelemetryChannelClient } from "vs/server/src/common/telemetry"; import { TelemetryChannelClient } from "vs/server/src/common/telemetry";
import { split } from "vs/server/src/common/util";
import "vs/workbench/contrib/localizations/browser/localizations.contribution"; import "vs/workbench/contrib/localizations/browser/localizations.contribution";
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService"; import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
class TelemetryService extends TelemetryChannelClient { class TelemetryService extends TelemetryChannelClient {
public constructor( public constructor(
@@ -21,6 +25,23 @@ class TelemetryService extends TelemetryChannelClient {
} }
} }
const TELEMETRY_SECTION_ID = "telemetry";
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
"id": TELEMETRY_SECTION_ID,
"order": 110,
"type": "object",
"title": localize("telemetryConfigurationTitle", "Telemetry"),
"properties": {
"telemetry.enableTelemetry": {
"type": "boolean",
"description": localize("telemetry.enableTelemetry", "Enable usage data and errors to be sent to a Microsoft online service."),
"default": true,
"tags": ["usesOnlineServices"]
}
}
});
class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService { class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
private readonly _onClose = new Emitter<void>(); private readonly _onClose = new Emitter<void>();
public readonly onClose = this._onClose.event; public readonly onClose = this._onClose.event;
@@ -49,7 +70,6 @@ class NodeProxyService extends NodeProxyChannelClient implements INodeProxyServi
registerSingleton(ILocalizationsService, LocalizationsService); registerSingleton(ILocalizationsService, LocalizationsService);
registerSingleton(INodeProxyService, NodeProxyService); registerSingleton(INodeProxyService, NodeProxyService);
registerSingleton(ITelemetryService, TelemetryService); registerSingleton(ITelemetryService, TelemetryService);
registerSingleton(IUploadService, UploadService, true);
/** /**
* This is called by vs/workbench/browser/web.main.ts after the workbench has * This is called by vs/workbench/browser/web.main.ts after the workbench has
@@ -64,6 +84,27 @@ export const initialize = async (services: ServiceCollection): Promise<void> =>
(event as any).ide = target.ide; (event as any).ide = target.ide;
(event as any).vscode = target.vscode; (event as any).vscode = target.vscode;
window.dispatchEvent(event); window.dispatchEvent(event);
if (!window.isSecureContext) {
(services.get(INotificationService) as INotificationService).notify({
severity: Severity.Warning,
message: "code-server is being accessed over an insecure domain. Some functionality may not work as expected.",
actions: {
primary: [{
id: "understand",
label: "I understand",
tooltip: "",
class: undefined,
enabled: true,
checked: true,
dispose: () => undefined,
run: () => {
return Promise.resolve();
}
}],
}
});
}
}; };
export interface Query { export interface Query {
@@ -79,7 +120,7 @@ export const withQuery = (url: string, replace: Query): string => {
const uri = URI.parse(url); const uri = URI.parse(url);
const query = { ...replace }; const query = { ...replace };
uri.query.split("&").forEach((kv) => { uri.query.split("&").forEach((kv) => {
const [key, value] = kv.split("=", 2); const [key, value] = split(kv, "=");
if (!(key in query)) { if (!(key in query)) {
query[key] = value; query[key] = value;
} }

View File

@@ -1,5 +1,5 @@
import { Emitter } from "vs/base/common/event"; import { Emitter } from "vs/base/common/event";
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from "vs/platform/instantiation/common/instantiation";
import { ExtHostNodeProxyShape, MainContext, MainThreadNodeProxyShape } from "vs/workbench/api/common/extHost.protocol"; import { ExtHostNodeProxyShape, MainContext, MainThreadNodeProxyShape } from "vs/workbench/api/common/extHost.protocol";
import { IExtHostRpcService } from "vs/workbench/api/common/extHostRpcService"; import { IExtHostRpcService } from "vs/workbench/api/common/extHostRpcService";
@@ -43,4 +43,4 @@ export class ExtHostNodeProxy implements ExtHostNodeProxyShape {
} }
export interface IExtHostNodeProxy extends ExtHostNodeProxy { } export interface IExtHostNodeProxy extends ExtHostNodeProxy { }
export const IExtHostNodeProxy = createDecorator<IExtHostNodeProxy>('IExtHostNodeProxy'); export const IExtHostNodeProxy = createDecorator<IExtHostNodeProxy>("IExtHostNodeProxy");

View File

@@ -4,9 +4,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'unsafe-inline'; manifest-src 'self'; img-src 'self';"> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'unsafe-inline'; manifest-src 'self'; img-src 'self';">
<title>Authenticate: code-server</title> <title>Authenticate: code-server</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon" /> <link rel="icon" href="./static/out/vs/server/src/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json"> <link rel="manifest" href="./manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="./static/out/vs/server/src/media/code-server.png" /> <link rel="apple-touch-icon" href="./static/out/vs/server/src/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes">
<link href="./static/out/vs/server/src/media/login.css" rel="stylesheet"> <link href="./static/out/vs/server/src/media/login.css" rel="stylesheet">
</head> </head>
<body> <body>

View File

@@ -1,372 +0,0 @@
import { DesktopDragAndDropData } from "vs/base/browser/ui/list/listView";
import { VSBuffer, VSBufferReadableStream } from "vs/base/common/buffer";
import { Disposable } from "vs/base/common/lifecycle";
import * as path from "vs/base/common/path";
import { URI } from "vs/base/common/uri";
import { generateUuid } from "vs/base/common/uuid";
import { IFileService } from "vs/platform/files/common/files";
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from "vs/platform/notification/common/notification";
import { IProgress, IProgressService, IProgressStep, ProgressLocation } from "vs/platform/progress/common/progress";
import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace";
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { ExplorerItem } from "vs/workbench/contrib/files/common/explorerModel";
import { IEditorGroup } from "vs/workbench/services/editor/common/editorGroupsService";
import { IEditorService } from "vs/workbench/services/editor/common/editorService";
export const IUploadService = createDecorator<IUploadService>("uploadService");
export interface IUploadService {
_serviceBrand: undefined;
handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void>;
handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void>;
}
export class UploadService extends Disposable implements IUploadService {
public _serviceBrand: undefined;
public upload: Upload;
public constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@IEditorService private readonly editorService: IEditorService,
) {
super();
this.upload = instantiationService.createInstance(Upload);
}
public async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
// TODO: should use the workspace for the editor it was dropped on?
const target = this.contextService.getWorkspace().folders[0].uri;
const uris = (await this.upload.uploadDropped(event, target)).map((u) => URI.file(u));
if (uris.length > 0) {
await this.workspacesService.addRecentlyOpened(uris.map((u) => ({ fileUri: u })));
}
const editors = uris.map((uri) => ({
resource: uri,
options: {
pinned: true,
index: targetIndex,
},
}));
const targetGroup = resolveTargetGroup();
this.editorService.openEditors(editors, targetGroup);
afterDrop(targetGroup);
}
public async handleExternalDrop(_data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
await this.upload.uploadDropped(originalEvent, target.resource);
}
}
/**
* There doesn't seem to be a provided type for entries, so here is an
* incomplete version.
*/
interface IEntry {
name: string;
isFile: boolean;
file: (cb: (file: File) => void) => void;
createReader: () => ({
readEntries: (cb: (entries: Array<IEntry>) => void) => void;
});
}
/**
* Handles file uploads.
*/
class Upload {
private readonly maxParallelUploads = 100;
private readonly uploadingFiles = new Map<string, Reader | undefined>();
private readonly fileQueue = new Map<string, File>();
private progress: IProgress<IProgressStep> | undefined;
private uploadPromise: Promise<string[]> | undefined;
private resolveUploadPromise: (() => void) | undefined;
private uploadedFilePaths = <string[]>[];
private _total = 0;
private _uploaded = 0;
private lastPercent = 0;
public constructor(
@INotificationService private notificationService: INotificationService,
@IProgressService private progressService: IProgressService,
@IFileService private fileService: IFileService,
) {}
/**
* Upload dropped files. This will try to upload everything it can. Errors
* will show via notifications. If an upload operation is ongoing, the files
* will be added to that operation.
*/
public async uploadDropped(event: DragEvent, uploadDir: URI): Promise<string[]> {
await this.queueFiles(event, uploadDir);
if (!this.uploadPromise) {
this.uploadPromise = this.progressService.withProgress({
cancellable: true,
location: ProgressLocation.Notification,
title: "Uploading files...",
}, (progress) => {
return new Promise((resolve): void => {
this.progress = progress;
this.resolveUploadPromise = (): void => {
const uploaded = this.uploadedFilePaths;
this.uploadPromise = undefined;
this.resolveUploadPromise = undefined;
this.uploadedFilePaths = [];
this.lastPercent = 0;
this._uploaded = 0;
this._total = 0;
resolve(uploaded);
};
});
}, () => this.cancel());
}
this.uploadFiles();
return this.uploadPromise;
}
/**
* Cancel all file uploads.
*/
public async cancel(): Promise<void> {
this.fileQueue.clear();
this.uploadingFiles.forEach((r) => r && r.abort());
}
private get total(): number { return this._total; }
private set total(total: number) {
this._total = total;
this.updateProgress();
}
private get uploaded(): number { return this._uploaded; }
private set uploaded(uploaded: number) {
this._uploaded = uploaded;
this.updateProgress();
}
private updateProgress(): void {
if (this.progress && this.total > 0) {
const percent = Math.floor((this.uploaded / this.total) * 100);
this.progress.report({ increment: percent - this.lastPercent });
this.lastPercent = percent;
}
}
/**
* Upload as many files as possible. When finished, resolve the upload
* promise.
*/
private uploadFiles(): void {
while (this.fileQueue.size > 0 && this.uploadingFiles.size < this.maxParallelUploads) {
const [path, file] = this.fileQueue.entries().next().value;
this.fileQueue.delete(path);
if (this.uploadingFiles.has(path)) {
this.notificationService.error(new Error(`Already uploading ${path}`));
} else {
this.uploadingFiles.set(path, undefined);
this.uploadFile(path, file).catch((error) => {
this.notificationService.error(error);
}).finally(() => {
this.uploadingFiles.delete(path);
this.uploadFiles();
});
}
}
if (this.fileQueue.size === 0 && this.uploadingFiles.size === 0) {
this.resolveUploadPromise!();
}
}
/**
* Upload a file, asking to override if necessary.
*/
private async uploadFile(filePath: string, file: File): Promise<void> {
const uri = URI.file(filePath);
if (await this.fileService.exists(uri)) {
const overwrite = await new Promise<boolean>((resolve): void => {
this.notificationService.prompt(
Severity.Error,
`${filePath} already exists. Overwrite?`,
[
{ label: "Yes", run: (): void => resolve(true) },
{ label: "No", run: (): void => resolve(false) },
],
{ onCancel: () => resolve(false) },
);
});
if (!overwrite) {
return;
}
}
const tempUri = uri.with({
path: path.join(
path.dirname(uri.path),
`.code-server-partial-upload-${path.basename(uri.path)}-${generateUuid()}`,
),
});
const reader = new Reader(file);
reader.on("data", (data) => {
if (data && data.byteLength > 0) {
this.uploaded += data.byteLength;
}
});
this.uploadingFiles.set(filePath, reader);
await this.fileService.writeFile(tempUri, reader);
if (reader.aborted) {
this.uploaded += (file.size - reader.offset);
await this.fileService.del(tempUri);
} else {
await this.fileService.move(tempUri, uri, true);
this.uploadedFilePaths.push(filePath);
}
}
/**
* Queue files from a drop event. We have to get the files first; we can't do
* it in tandem with uploading or the entries will disappear.
*/
private async queueFiles(event: DragEvent, uploadDir: URI): Promise<void> {
const promises: Array<Promise<void>> = [];
for (let i = 0; event.dataTransfer && event.dataTransfer.items && i < event.dataTransfer.items.length; ++i) {
const item = event.dataTransfer.items[i];
if (typeof item.webkitGetAsEntry === "function") {
promises.push(this.traverseItem(item.webkitGetAsEntry(), uploadDir.fsPath));
} else {
const file = item.getAsFile();
if (file) {
this.addFile(uploadDir.fsPath + "/" + file.name, file);
}
}
}
await Promise.all(promises);
}
/**
* Traverses an entry and add files to the queue.
*/
private async traverseItem(entry: IEntry, path: string): Promise<void> {
if (entry.isFile) {
return new Promise<void>((resolve): void => {
entry.file((file) => {
resolve(this.addFile(path + "/" + file.name, file));
});
});
}
path += "/" + entry.name;
await new Promise((resolve): void => {
const promises: Array<Promise<void>> = [];
const dirReader = entry.createReader();
// According to the spec, readEntries() must be called until it calls
// the callback with an empty array.
const readEntries = (): void => {
dirReader.readEntries((entries) => {
if (entries.length === 0) {
Promise.all(promises).then(resolve).catch((error) => {
this.notificationService.error(error);
resolve();
});
} else {
promises.push(...entries.map((c) => this.traverseItem(c, path)));
readEntries();
}
});
};
readEntries();
});
}
/**
* Add a file to the queue.
*/
private addFile(path: string, file: File): void {
this.total += file.size;
this.fileQueue.set(path, file);
}
}
class Reader implements VSBufferReadableStream {
private _offset = 0;
private readonly size = 32000; // ~32kb max while reading in the file.
private _aborted = false;
private readonly reader = new FileReader();
private paused = true;
private buffer?: VSBuffer;
private callbacks = new Map<string, Array<(...args: any[]) => void>>();
public constructor(private readonly file: File) {
this.reader.addEventListener("load", this.onLoad);
}
public get offset(): number { return this._offset; }
public get aborted(): boolean { return this._aborted; }
public on(event: "data" | "error" | "end", callback: (...args:any[]) => void): void {
if (!this.callbacks.has(event)) {
this.callbacks.set(event, []);
}
this.callbacks.get(event)!.push(callback);
if (this.aborted) {
return this.emit("error", new Error("stream has been aborted"));
} else if (this.done) {
return this.emit("error", new Error("stream has ended"));
} else if (event === "end") { // Once this is being listened to we can safely start outputting data.
this.resume();
}
}
public abort = (): void => {
this._aborted = true;
this.reader.abort();
this.reader.removeEventListener("load", this.onLoad);
this.emit("end");
}
public pause(): void {
this.paused = true;
}
public resume(): void {
if (this.paused) {
this.paused = false;
this.readNextChunk();
}
}
public destroy(): void {
this.abort();
}
private onLoad = (): void => {
this.buffer = VSBuffer.wrap(new Uint8Array(this.reader.result as ArrayBuffer));
if (!this.paused) {
this.readNextChunk();
}
}
private readNextChunk(): void {
if (this.buffer) {
this._offset += this.buffer.byteLength;
this.emit("data", this.buffer);
this.buffer = undefined;
}
if (!this.paused) { // Could be paused during the data event.
if (this.done) {
this.emit("end");
} else {
this.reader.readAsArrayBuffer(this.file.slice(this.offset, this.offset + this.size));
}
}
}
private emit(event: "data" | "error" | "end", ...args: any[]): void {
if (this.callbacks.has(event)) {
this.callbacks.get(event)!.forEach((cb) => cb(...args));
}
}
private get done(): boolean {
return this.offset >= this.file.size;
}
}

View File

@@ -7,23 +7,6 @@
<!-- Disable pinch zooming --> <!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- Content Security Policy -->
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
img-src 'self' https: data: blob:;
media-src 'none';
script-src 'self' 'unsafe-eval' https: 'sha256-bpJydy1E+3Mx9MyBtkOIA3yyzM2wdyIz115+Sgq21+0=' 'sha256-meDZW3XhN5JmdjFUrWGhTouRKBiWYtXHltaKnqn/WMo=';
child-src 'self';
frame-src 'self';
worker-src 'self';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws: wss: https:;
font-src 'self' blob:;
manifest-src 'self';
">
<!-- Workbench Configuration --> <!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}"> <meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
@@ -36,14 +19,14 @@
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}"> <meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS --> <!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="./favicon.ico" type="image/x-icon" /> <link rel="icon" href="./static-{{COMMIT}}/out/vs/server/src/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json"> <link rel="manifest" href="./manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/out/vs/server/src/media/code-server.png" />
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.css"> <link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.css">
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/out/vs/server/src/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- Prefetch to avoid waterfall --> <!-- Prefetch to avoid waterfall -->
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js"> <link rel="prefetch" href="./static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js">
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js">
</head> </head>
<body aria-label=""> <body aria-label="">
@@ -89,8 +72,8 @@
'xterm': `${staticBase}/node_modules/xterm/lib/xterm.js`, 'xterm': `${staticBase}/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-search': `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, 'xterm-addon-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'xterm-addon-webgl': `${staticBase}/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'semver-umd': `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`, 'semver-umd': `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`,
'@microsoft/applicationinsights-web': `${staticBase}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
}, },
'vs/nls': nlsConfig, 'vs/nls': nlsConfig,
}; };

View File

@@ -7,23 +7,6 @@
<!-- Disable pinch zooming --> <!-- Disable pinch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- Content Security Policy -->
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
img-src 'self' https: data: blob:;
media-src 'none';
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'unsafe-inline';
child-src 'self';
frame-src 'self' https://*.vscode-webview-test.com;
worker-src 'self';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws: wss: https:;
font-src 'self' blob:;
manifest-src 'self';
">
<!-- Workbench Configuration --> <!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}"> <meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
@@ -36,8 +19,8 @@
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}"> <meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS --> <!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="./favicon.ico" type="image/x-icon" /> <link rel="icon" href="./static/out/vs/server/src/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json"> <link rel="manifest" href="./manifest.json" crossorigin="use-credentials">
</head> </head>
<body aria-label=""> <body aria-label="">
@@ -58,8 +41,8 @@
'xterm': `${staticBase}/node_modules/xterm/lib/xterm.js`, 'xterm': `${staticBase}/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-search': `${staticBase}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, 'xterm-addon-web-links': `${staticBase}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'xterm-addon-webgl': `${staticBase}/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'semver-umd': `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`, 'semver-umd': `${staticBase}/node_modules/semver-umd/lib/semver-umd.js`,
'@microsoft/applicationinsights-web': `${staticBase}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
}, },
}; };
</script> </script>

10
src/common/util.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* Split a string up to the delimiter. If the delimiter doesn't exist the first
* item will have all the text and the second item will be an empty string.
*/
export const split = (str: string, delimiter: string): [string, string] => {
const index = str.indexOf(delimiter);
return index !== -1
? [str.substring(0, index).trim(), str.substring(index + 1)]
: [str, ""];
};

View File

@@ -6,7 +6,7 @@
"background-color": "#fff", "background-color": "#fff",
"description": "Run VS Code on a remote server.", "description": "Run VS Code on a remote server.",
"icons": [{ "icons": [{
"src": "./static/out/vs/server/src/media/code-server.png", "src": "./code-server.png",
"sizes": "384x384", "sizes": "384x384",
"type": "image/png" "type": "image/png"
}] }]

View File

@@ -1,23 +1,26 @@
import * as path from "path"; import * as path from "path";
import { VSBuffer } from "vs/base/common/buffer"; import { VSBuffer, VSBufferReadableStream } from "vs/base/common/buffer";
import { Emitter, Event } from "vs/base/common/event"; import { Emitter, Event } from "vs/base/common/event";
import { IDisposable } from "vs/base/common/lifecycle"; import { IDisposable } from "vs/base/common/lifecycle";
import { OS } from "vs/base/common/platform"; import { OS } from "vs/base/common/platform";
import { ReadableStreamEventPayload } from "vs/base/common/stream";
import { URI, UriComponents } from "vs/base/common/uri"; import { URI, UriComponents } from "vs/base/common/uri";
import { transformOutgoingURIs } from "vs/base/common/uriIpc"; import { transformOutgoingURIs } from "vs/base/common/uriIpc";
import { IServerChannel } from "vs/base/parts/ipc/common/ipc"; import { IServerChannel } from "vs/base/parts/ipc/common/ipc";
import { IDiagnosticInfo } from "vs/platform/diagnostics/common/diagnostics"; import { IDiagnosticInfo } from "vs/platform/diagnostics/common/diagnostics";
import { IEnvironmentService } from "vs/platform/environment/common/environment"; import { IEnvironmentService } from "vs/platform/environment/common/environment";
import { ExtensionIdentifier, IExtensionDescription } from "vs/platform/extensions/common/extensions"; import { ExtensionIdentifier, IExtensionDescription } from "vs/platform/extensions/common/extensions";
import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileType, IStat, IWatchOptions } from "vs/platform/files/common/files"; import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileReadStreamOptions, FileType, FileWriteOptions, IStat, IWatchOptions } from "vs/platform/files/common/files";
import { createReadStream } from "vs/platform/files/common/io";
import { DiskFileSystemProvider } from "vs/platform/files/node/diskFileSystemProvider"; import { DiskFileSystemProvider } from "vs/platform/files/node/diskFileSystemProvider";
import { ILogService } from "vs/platform/log/common/log"; import { ILogService } from "vs/platform/log/common/log";
import product from "vs/platform/product/common/product"; import product from "vs/platform/product/common/product";
import { IRemoteAgentEnvironment } from "vs/platform/remote/common/remoteAgentEnvironment"; import { IRemoteAgentEnvironment, RemoteAgentConnectionContext } from "vs/platform/remote/common/remoteAgentEnvironment";
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry"; import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
import { INodeProxyService } from "vs/server/src/common/nodeProxy"; import { INodeProxyService } from "vs/server/src/common/nodeProxy";
import { getTranslations } from "vs/server/src/node/nls"; import { getTranslations } from "vs/server/src/node/nls";
import { getUriTransformer, localRequire } from "vs/server/src/node/util"; import { getUriTransformer, localRequire } from "vs/server/src/node/util";
import { IFileChangeDto } from "vs/workbench/api/common/extHost.protocol";
import { ExtensionScanner, ExtensionScannerInput } from "vs/workbench/services/extensions/node/extensionPoints"; import { ExtensionScanner, ExtensionScannerInput } from "vs/workbench/services/extensions/node/extensionPoints";
/** /**
@@ -42,7 +45,7 @@ class Watcher extends DiskFileSystemProvider {
} }
} }
export class FileProviderChannel implements IServerChannel, IDisposable { export class FileProviderChannel implements IServerChannel<RemoteAgentConnectionContext>, IDisposable {
private readonly provider: DiskFileSystemProvider; private readonly provider: DiskFileSystemProvider;
private readonly watchers = new Map<string, Watcher>(); private readonly watchers = new Map<string, Watcher>();
@@ -53,48 +56,67 @@ export class FileProviderChannel implements IServerChannel, IDisposable {
this.provider = new DiskFileSystemProvider(this.logService); this.provider = new DiskFileSystemProvider(this.logService);
} }
public listen(context: any, event: string, args?: any): Event<any> { public listen(context: RemoteAgentConnectionContext, event: string, args?: any): Event<any> {
switch (event) { switch (event) {
// This is where the actual file changes are sent. The watch method just case "filechange": return this.filechange(context, args[0]);
// adds things that will fire here. That means we have to split up case "readFileStream": return this.readFileStream(args[0], args[1]);
// watchers based on the session otherwise sessions would get events for
// other sessions. There is also no point in having the watcher unless
// something is listening. I'm not sure there is a different way to
// dispose, anyway.
case "filechange":
const session = args[0];
const emitter = new Emitter({
onFirstListenerAdd: () => {
const provider = new Watcher(this.logService);
this.watchers.set(session, provider);
const transformer = getUriTransformer(context.remoteAuthority);
provider.onDidChangeFile((events) => {
emitter.fire(events.map((event) => ({
...event,
resource: transformer.transformOutgoing(event.resource),
})));
});
provider.onDidErrorOccur((event) => emitter.fire(event));
},
onLastListenerRemove: () => {
this.watchers.get(session)!.dispose();
this.watchers.delete(session);
},
});
return emitter.event;
} }
throw new Error(`Invalid listen "${event}"`); throw new Error(`Invalid listen "${event}"`);
} }
private filechange(context: RemoteAgentConnectionContext, session: string): Event<IFileChangeDto[]> {
const emitter = new Emitter<IFileChangeDto[]>({
onFirstListenerAdd: () => {
const provider = new Watcher(this.logService);
this.watchers.set(session, provider);
const transformer = getUriTransformer(context.remoteAuthority);
provider.onDidChangeFile((events) => {
emitter.fire(events.map((event) => ({
...event,
resource: transformer.transformOutgoing(event.resource),
})));
});
provider.onDidErrorOccur((event) => this.logService.error(event));
},
onLastListenerRemove: () => {
this.watchers.get(session)!.dispose();
this.watchers.delete(session);
},
});
return emitter.event;
}
private readFileStream(resource: UriComponents, opts: FileReadStreamOptions): Event<ReadableStreamEventPayload<VSBuffer>> {
let fileStream: VSBufferReadableStream | undefined;
const emitter = new Emitter<ReadableStreamEventPayload<VSBuffer>>({
onFirstListenerAdd: () => {
if (!fileStream) {
fileStream = createReadStream(this.provider, this.transform(resource), {
...opts,
bufferSize: 64 * 1024, // From DiskFileSystemProvider
});
fileStream.on("data", (data) => emitter.fire(data));
fileStream.on("error", (error) => emitter.fire(error));
fileStream.on("end", () => emitter.fire("end"));
}
},
onLastListenerRemove: () => fileStream && fileStream.destroy(),
});
return emitter.event;
}
public call(_: unknown, command: string, args?: any): Promise<any> { public call(_: unknown, command: string, args?: any): Promise<any> {
switch (command) { switch (command) {
case "stat": return this.stat(args[0]); case "stat": return this.stat(args[0]);
case "open": return this.open(args[0], args[1]); case "open": return this.open(args[0], args[1]);
case "close": return this.close(args[0]); case "close": return this.close(args[0]);
case "read": return this.read(args[0], args[1], args[2]); case "read": return this.read(args[0], args[1], args[2]);
case "readFile": return this.readFile(args[0]);
case "write": return this.write(args[0], args[1], args[2], args[3], args[4]); case "write": return this.write(args[0], args[1], args[2], args[3], args[4]);
case "writeFile": return this.writeFile(args[0], args[1], args[2]);
case "delete": return this.delete(args[0], args[1]); case "delete": return this.delete(args[0], args[1]);
case "mkdir": return this.mkdir(args[0]); case "mkdir": return this.mkdir(args[0]);
case "readdir": return this.readdir(args[0]); case "readdir": return this.readdir(args[0]);
@@ -130,10 +152,18 @@ export class FileProviderChannel implements IServerChannel, IDisposable {
return [buffer, bytesRead]; return [buffer, bytesRead];
} }
private async readFile(resource: UriComponents): Promise<VSBuffer> {
return VSBuffer.wrap(await this.provider.readFile(this.transform(resource)));
}
private write(fd: number, pos: number, buffer: VSBuffer, offset: number, length: number): Promise<number> { private write(fd: number, pos: number, buffer: VSBuffer, offset: number, length: number): Promise<number> {
return this.provider.write(fd, pos, buffer.buffer, offset, length); return this.provider.write(fd, pos, buffer.buffer, offset, length);
} }
private writeFile(resource: UriComponents, buffer: VSBuffer, opts: FileWriteOptions): Promise<void> {
return this.provider.writeFile(this.transform(resource), buffer.buffer, opts);
}
private async delete(resource: UriComponents, opts: FileDeleteOptions): Promise<void> { private async delete(resource: UriComponents, opts: FileDeleteOptions): Promise<void> {
return this.provider.delete(this.transform(resource), opts); return this.provider.delete(this.transform(resource), opts);
} }

View File

@@ -47,7 +47,6 @@ const getArgs = (): Args => {
case "wait": case "wait":
case "disable-gpu": case "disable-gpu":
// TODO: pretty sure these don't work but not 100%. // TODO: pretty sure these don't work but not 100%.
case "max-memory":
case "prof-startup": case "prof-startup":
case "inspect-extensions": case "inspect-extensions":
case "inspect-brk-extensions": case "inspect-brk-extensions":
@@ -82,15 +81,14 @@ const getArgs = (): Args => {
return validatePaths(args); return validatePaths(args);
}; };
const startVscode = async (): Promise<void | void[]> => { const startVscode = async (args: Args): Promise<void | void[]> => {
const args = getArgs();
const extra = args["_"] || []; const extra = args["_"] || [];
const options = { const options = {
auth: args.auth || AuthType.Password, auth: args.auth || AuthType.Password,
basePath: args["base-path"], basePath: args["base-path"],
cert: args.cert, cert: args.cert,
certKey: args["cert-key"], certKey: args["cert-key"],
folderUri: extra.length > 1 ? extra[extra.length - 1] : undefined, openUri: extra.length > 1 ? extra[extra.length - 1] : undefined,
host: args.host, host: args.host,
password: process.env.PASSWORD, password: process.env.PASSWORD,
}; };
@@ -155,8 +153,7 @@ const startVscode = async (): Promise<void | void[]> => {
} }
}; };
const startCli = (): boolean | Promise<void> => { const startCli = (args: Args): boolean | Promise<void> => {
const args = getArgs();
if (args.help) { if (args.help) {
const executable = `${product.applicationName}${os.platform() === "win32" ? ".exe" : ""}`; const executable = `${product.applicationName}${os.platform() === "win32" ? ".exe" : ""}`;
console.log(buildHelpMessage(product.nameLong, executable, product.codeServerVersion, OPTIONS, false)); console.log(buildHelpMessage(product.nameLong, executable, product.codeServerVersion, OPTIONS, false));
@@ -196,14 +193,17 @@ const startCli = (): boolean | Promise<void> => {
export class WrapperProcess { export class WrapperProcess {
private process?: cp.ChildProcess; private process?: cp.ChildProcess;
private started?: Promise<void>; private started?: Promise<void>;
private currentVersion = product.codeServerVersion;
public constructor() { public constructor(private readonly args: Args) {
ipcMain.onMessage(async (message) => { ipcMain.onMessage(async (message) => {
switch (message) { switch (message.type) {
case "relaunch": case "relaunch":
logger.info("Relaunching..."); logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`);
this.currentVersion = message.version;
this.started = undefined; this.started = undefined;
if (this.process) { if (this.process) {
this.process.removeAllListeners();
this.process.kill(); this.process.kill();
} }
try { try {
@@ -223,17 +223,35 @@ export class WrapperProcess {
public start(): Promise<void> { public start(): Promise<void> {
if (!this.started) { if (!this.started) {
const child = this.spawn(); const child = this.spawn();
this.started = ipcMain.handshake(child); this.started = ipcMain.handshake(child).then(() => {
child.once("exit", (code) => exit(code!));
});
this.process = child; this.process = child;
} }
return this.started; return this.started;
} }
private spawn(): cp.ChildProcess { private spawn(): cp.ChildProcess {
return cp.spawn(process.argv[0], process.argv.slice(1), { // Flags to pass along to the Node binary. We use the environment variable
// since otherwise the code-server binary will swallow them.
const maxMemory = this.args["max-memory"] || 2048;
let nodeOptions = `${process.env.NODE_OPTIONS || ""} ${this.args["js-flags"] || ""}`;
if (!/max_old_space_size=(\d+)/g.exec(nodeOptions)) {
nodeOptions += ` --max_old_space_size=${maxMemory}`;
}
// If we're using loose files then we need to specify the path. If we're in
// the binary we need to let the binary determine the path (via nbin) since
// it could be different between binaries which presents a problem when
// upgrading (different version numbers or different staging directories).
const isBinary = (global as any).NBIN_LOADED;
return cp.spawn(process.argv[0], process.argv.slice(isBinary ? 2 : 1), {
env: { env: {
...process.env, ...process.env,
LAUNCH_VSCODE: "true", LAUNCH_VSCODE: "true",
NBIN_BYPASS: undefined,
VSCODE_PARENT_PID: process.pid.toString(),
NODE_OPTIONS: nodeOptions,
}, },
stdio: ["inherit", "inherit", "inherit", "ipc"], stdio: ["inherit", "inherit", "inherit", "ipc"],
}); });
@@ -241,11 +259,12 @@ export class WrapperProcess {
} }
const main = async(): Promise<boolean | void | void[]> => { const main = async(): Promise<boolean | void | void[]> => {
const args = getArgs();
if (process.env.LAUNCH_VSCODE) { if (process.env.LAUNCH_VSCODE) {
await ipcMain.handshake(); await ipcMain.handshake();
return startVscode(); return startVscode(args);
} }
return startCli() || new WrapperProcess().start(); return startCli(args) || new WrapperProcess(args).start();
}; };
const exit = process.exit; const exit = process.exit;
@@ -254,6 +273,20 @@ process.exit = function (code?: number) {
console.warn(err.stack); console.warn(err.stack);
} as (code?: number) => never; } as (code?: number) => never;
// Copy the extension host behavior of killing oneself if the parent dies. This
// also exists in bootstrap-fork.js but spawning with that won't work because we
// override process.exit.
if (typeof process.env.VSCODE_PARENT_PID !== "undefined") {
const parentPid = parseInt(process.env.VSCODE_PARENT_PID, 10);
setInterval(() => {
try {
process.kill(parentPid, 0); // Throws an exception if the process doesn't exist anymore.
} catch (e) {
exit();
}
}, 5000);
}
// It's possible that the pipe has closed (for example if you run code-server // It's possible that the pipe has closed (for example if you run code-server
// --version | head -1). Assume that means we're done. // --version | head -1). Assume that means we're done.
if (!process.stdout.isTTY) { if (!process.stdout.isTTY) {

View File

@@ -3,14 +3,81 @@ import * as https from "https";
import * as http from "http"; import * as http from "http";
import * as os from "os"; import * as os from "os";
export class TelemetryClient implements appInsights.TelemetryClient { class Channel {
public get _sender() {
throw new Error("unimplemented");
}
public get _buffer() {
throw new Error("unimplemented");
}
public setUseDiskRetryCaching(): void {
throw new Error("unimplemented");
}
public send(): void {
throw new Error("unimplemented");
}
public triggerSend(): void {
throw new Error("unimplemented");
}
}
export class TelemetryClient {
public context: any = undefined;
public commonProperties: any = undefined;
public config: any = {}; public config: any = {};
public channel = { public channel: any = new Channel();
setUseDiskRetryCaching: (): void => undefined,
};
public trackEvent(options: appInsights.EventTelemetry): void { public addTelemetryProcessor(): void {
throw new Error("unimplemented");
}
public clearTelemetryProcessors(): void {
throw new Error("unimplemented");
}
public runTelemetryProcessors(): void {
throw new Error("unimplemented");
}
public trackTrace(): void {
throw new Error("unimplemented");
}
public trackMetric(): void {
throw new Error("unimplemented");
}
public trackException(): void {
throw new Error("unimplemented");
}
public trackRequest(): void {
throw new Error("unimplemented");
}
public trackDependency(): void {
throw new Error("unimplemented");
}
public track(): void {
throw new Error("unimplemented");
}
public trackNodeHttpRequestSync(): void {
throw new Error("unimplemented");
}
public trackNodeHttpRequest(): void {
throw new Error("unimplemented");
}
public trackNodeHttpDependency(): void {
throw new Error("unimplemented");
}
public trackEvent(options: appInsights.Contracts.EventTelemetry): void {
if (!options.properties) { if (!options.properties) {
options.properties = {}; options.properties = {};
} }
@@ -43,13 +110,13 @@ export class TelemetryClient implements appInsights.TelemetryClient {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}); });
request.on("error", () => { /* We don't care. */ }); request.on("error", () => { /* We don"t care. */ });
request.write(JSON.stringify(options)); request.write(JSON.stringify(options));
request.end(); request.end();
} catch (error) {} } catch (error) {}
} }
public flush(options: appInsights.FlushOptions): void { public flush(options: { callback: (v: string) => void }): void {
if (options.callback) { if (options.callback) {
options.callback(""); options.callback("");
} }

View File

@@ -6,7 +6,12 @@ enum ControlMessage {
okFromChild = "ok<", okFromChild = "ok<",
} }
export type Message = "relaunch"; interface RelaunchMessage {
type: "relaunch";
version: string;
}
export type Message = RelaunchMessage;
class IpcMain { class IpcMain {
protected readonly _onMessage = new Emitter<Message>(); protected readonly _onMessage = new Emitter<Message>();
@@ -41,11 +46,15 @@ class IpcMain {
}); });
} }
public relaunch(): void { public relaunch(version: string): void {
this.send({ type: "relaunch", version });
}
private send(message: Message): void {
if (!process.send) { if (!process.send) {
throw new Error("Not a child process with IPC enabled"); throw new Error("Not a child process with IPC enabled");
} }
process.send("relaunch"); process.send(message);
} }
} }

View File

@@ -144,7 +144,7 @@ const extractTar = async (tarPath: string, targetPath: string, options: IExtract
return fail(new Error(nls.localize("invalid file", "Error extracting {0}. Invalid file.", fileName))); return fail(new Error(nls.localize("invalid file", "Error extracting {0}. Invalid file.", fileName)));
} }
await mkdirp(targetDirName, undefined, token); await mkdirp(targetDirName, undefined);
const fstream = fs.createWriteStream(targetFileName, { mode: header.mode }); const fstream = fs.createWriteStream(targetFileName, { mode: header.mode });
fstream.once("close", () => next()); fstream.once("close", () => next());

View File

@@ -17,7 +17,7 @@ import { generateUuid } from "vs/base/common/uuid";
import { getMachineId } from 'vs/base/node/id'; import { getMachineId } from 'vs/base/node/id';
import { NLSConfiguration } from "vs/base/node/languagePacks"; import { NLSConfiguration } from "vs/base/node/languagePacks";
import { mkdirp, rimraf } from "vs/base/node/pfs"; import { mkdirp, rimraf } from "vs/base/node/pfs";
import { ClientConnectionEvent, IPCServer } from "vs/base/parts/ipc/common/ipc"; import { ClientConnectionEvent, IPCServer, IServerChannel } from "vs/base/parts/ipc/common/ipc";
import { createChannelReceiver } from "vs/base/parts/ipc/node/ipc"; import { createChannelReceiver } from "vs/base/parts/ipc/node/ipc";
import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner"; import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner";
import { IConfigurationService } from "vs/platform/configuration/common/configuration"; import { IConfigurationService } from "vs/platform/configuration/common/configuration";
@@ -43,6 +43,7 @@ import { SpdLogService } from "vs/platform/log/node/spdlogService";
import product from 'vs/platform/product/common/product'; import product from 'vs/platform/product/common/product';
import { IProductService } from "vs/platform/product/common/productService"; import { IProductService } from "vs/platform/product/common/productService";
import { ConnectionType, ConnectionTypeRequest } from "vs/platform/remote/common/remoteAgentConnection"; import { ConnectionType, ConnectionTypeRequest } from "vs/platform/remote/common/remoteAgentConnection";
import { RemoteAgentConnectionContext } from "vs/platform/remote/common/remoteAgentEnvironment";
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/platform/remote/common/remoteAgentFileSystemChannel"; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/platform/remote/common/remoteAgentFileSystemChannel";
import { IRequestService } from "vs/platform/request/common/request"; import { IRequestService } from "vs/platform/request/common/request";
import { RequestChannel } from "vs/platform/request/common/requestIpc"; import { RequestChannel } from "vs/platform/request/common/requestIpc";
@@ -56,13 +57,14 @@ import { resolveCommonProperties } from "vs/platform/telemetry/node/commonProper
import { UpdateChannel } from "vs/platform/update/electron-main/updateIpc"; import { UpdateChannel } from "vs/platform/update/electron-main/updateIpc";
import { INodeProxyService, NodeProxyChannel } from "vs/server/src/common/nodeProxy"; import { INodeProxyService, NodeProxyChannel } from "vs/server/src/common/nodeProxy";
import { TelemetryChannel } from "vs/server/src/common/telemetry"; import { TelemetryChannel } from "vs/server/src/common/telemetry";
import { split } from "vs/server/src/common/util";
import { ExtensionEnvironmentChannel, FileProviderChannel, NodeProxyService } from "vs/server/src/node/channel"; import { ExtensionEnvironmentChannel, FileProviderChannel, NodeProxyService } from "vs/server/src/node/channel";
import { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/src/node/connection"; import { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/src/node/connection";
import { TelemetryClient } from "vs/server/src/node/insights"; import { TelemetryClient } from "vs/server/src/node/insights";
import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/src/node/nls"; import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/src/node/nls";
import { Protocol } from "vs/server/src/node/protocol"; import { Protocol } from "vs/server/src/node/protocol";
import { UpdateService } from "vs/server/src/node/update"; import { UpdateService } from "vs/server/src/node/update";
import { AuthType, getMediaMime, getUriTransformer, localRequire, tmpdir } from "vs/server/src/node/util"; import { AuthType, getMediaMime, getUriTransformer, hash, localRequire, tmpdir } from "vs/server/src/node/util";
import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService"; import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService";
import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api"; import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api";
@@ -100,6 +102,10 @@ export interface LoginPayload {
password?: string; password?: string;
} }
export interface AuthPayload {
key?: string[];
}
export class HttpError extends Error { export class HttpError extends Error {
public constructor(message: string, public readonly code: number) { public constructor(message: string, public readonly code: number) {
super(message); super(message);
@@ -115,7 +121,7 @@ export interface ServerOptions {
readonly connectionToken?: string; readonly connectionToken?: string;
readonly cert?: string; readonly cert?: string;
readonly certKey?: string; readonly certKey?: string;
readonly folderUri?: string; readonly openUri?: string;
readonly host?: string; readonly host?: string;
readonly password?: string; readonly password?: string;
readonly port?: number; readonly port?: number;
@@ -136,6 +142,7 @@ export abstract class Server {
host: options.auth === "password" && options.cert ? "0.0.0.0" : "localhost", host: options.auth === "password" && options.cert ? "0.0.0.0" : "localhost",
...options, ...options,
basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "", basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "",
password: options.password ? hash(options.password) : undefined,
}; };
this.protocol = this.options.cert ? "https" : "http"; this.protocol = this.options.cert ? "https" : "http";
if (this.protocol === "https") { if (this.protocol === "https") {
@@ -212,8 +219,8 @@ export abstract class Server {
} }
protected withBase(request: http.IncomingMessage, path: string): string { protected withBase(request: http.IncomingMessage, path: string): string {
const split = request.url ? request.url.split("?", 2) : []; const [, query] = request.url ? split(request.url, "?") : [];
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${split.length === 2 ? `?${split[1]}` : ""}`; return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${query ? `?${query}` : ""}`;
} }
private isAllowedRequestPath(path: string): boolean { private isAllowedRequestPath(path: string): boolean {
@@ -290,8 +297,10 @@ export abstract class Server {
switch (base) { switch (base) {
case "/": case "/":
switch (requestPath) { switch (requestPath) {
case "/favicon.ico": // NOTE: This must be served at the correct location based on the
// start_url in the manifest.
case "/manifest.json": case "/manifest.json":
case "/code-server.png":
const response = await this.getResource(this.serverRoot, "media", requestPath); const response = await this.getResource(this.serverRoot, "media", requestPath);
response.cache = true; response.cache = true;
return response; return response;
@@ -356,16 +365,25 @@ export abstract class Server {
} }
private async tryLogin(request: http.IncomingMessage): Promise<Response> { private async tryLogin(request: http.IncomingMessage): Promise<Response> {
if (this.authenticate(request) && (request.method === "GET" || request.method === "POST")) { const redirect = (password: string | true) => {
return { redirect: "/" }; return {
redirect: "/",
headers: typeof password === "string"
? { "Set-Cookie": `key=${password}; Path=${this.options.basePath || "/"}; HttpOnly; SameSite=strict` }
: {},
};
};
const providedPassword = this.authenticate(request);
if (providedPassword && (request.method === "GET" || request.method === "POST")) {
return redirect(providedPassword);
} }
if (request.method === "POST") { if (request.method === "POST") {
const data = await this.getData<LoginPayload>(request); const data = await this.getData<LoginPayload>(request);
if (this.authenticate(request, data)) { const password = this.authenticate(request, {
return { key: typeof data.password === "string" ? [hash(data.password)] : undefined,
redirect: "/", });
headers: { "Set-Cookie": `password=${data.password}` } if (password) {
}; return redirect(password);
} }
console.error("Failed login attempt", JSON.stringify({ console.error("Failed login attempt", JSON.stringify({
xForwardedFor: request.headers["x-forwarded-for"], xForwardedFor: request.headers["x-forwarded-for"],
@@ -425,23 +443,33 @@ export abstract class Server {
: Promise.resolve({} as T); : Promise.resolve({} as T);
} }
private authenticate(request: http.IncomingMessage, payload?: LoginPayload): boolean { private authenticate(request: http.IncomingMessage, payload?: AuthPayload): string | boolean {
if (this.options.auth !== "password") { if (this.options.auth === "none") {
return true; return true;
} }
const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index"); const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index");
if (typeof payload === "undefined") { if (typeof payload === "undefined") {
payload = this.parseCookies<LoginPayload>(request); payload = this.parseCookies<AuthPayload>(request);
} }
return !!this.options.password && safeCompare(payload.password || "", this.options.password); if (this.options.password && payload.key) {
for (let i = 0; i < payload.key.length; ++i) {
if (safeCompare(payload.key[i], this.options.password)) {
return payload.key[i];
}
}
}
return false;
} }
private parseCookies<T extends object>(request: http.IncomingMessage): T { private parseCookies<T extends object>(request: http.IncomingMessage): T {
const cookies: { [key: string]: string } = {}; const cookies: { [key: string]: string[] } = {};
if (request.headers.cookie) { if (request.headers.cookie) {
request.headers.cookie.split(";").forEach((keyValue) => { request.headers.cookie.split(";").forEach((keyValue) => {
const [key, value] = keyValue.split("=", 2); const [key, value] = split(keyValue, "=");
cookies[key.trim()] = decodeURI(value); if (!cookies[key]) {
cookies[key] = [];
}
cookies[key].push(decodeURI(value));
}); });
} }
return cookies as T; return cookies as T;
@@ -460,7 +488,7 @@ interface Settings {
export class MainServer extends Server { export class MainServer extends Server {
public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>(); public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
public readonly onDidClientConnect = this._onDidClientConnect.event; public readonly onDidClientConnect = this._onDidClientConnect.event;
private readonly ipc = new IPCServer(this.onDidClientConnect); private readonly ipc = new IPCServer<RemoteAgentConnectionContext>(this.onDidClientConnect);
private readonly maxExtraOfflineConnections = 0; private readonly maxExtraOfflineConnections = 0;
private readonly connections = new Map<ConnectionType, Map<string, Connection>>(); private readonly connections = new Map<ConnectionType, Map<string, Connection>>();
@@ -474,6 +502,9 @@ export class MainServer extends Server {
private readonly proxyTimeout = 5000; private readonly proxyTimeout = 5000;
private settings: Settings = {}; private settings: Settings = {};
private heartbeatTimer?: NodeJS.Timeout;
private heartbeatInterval = 60000;
private lastHeartbeat = 0;
public constructor(options: ServerOptions, args: ParsedArgs) { public constructor(options: ServerOptions, args: ParsedArgs) {
super(options); super(options);
@@ -491,6 +522,7 @@ export class MainServer extends Server {
} }
protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> { protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
this.heartbeat();
if (!parsedUrl.query.reconnectionToken) { if (!parsedUrl.query.reconnectionToken) {
throw new Error("Reconnection token is missing from query parameters"); throw new Error("Reconnection token is missing from query parameters");
} }
@@ -514,12 +546,13 @@ export class MainServer extends Server {
parsedUrl: url.UrlWithParsedQuery, parsedUrl: url.UrlWithParsedQuery,
request: http.IncomingMessage, request: http.IncomingMessage,
): Promise<Response> { ): Promise<Response> {
this.heartbeat();
switch (base) { switch (base) {
case "/": return this.getRoot(request, parsedUrl); case "/": return this.getRoot(request, parsedUrl);
case "/resource": case "/resource":
case "/vscode-remote-resource": case "/vscode-remote-resource":
if (typeof parsedUrl.query.path === "string") { if (typeof parsedUrl.query.path === "string") {
return this.getResource(parsedUrl.query.path); return this.getAnyResource(parsedUrl.query.path);
} }
break; break;
case "/tar": case "/tar":
@@ -546,9 +579,9 @@ export class MainServer extends Server {
util.promisify(fs.readFile)(filePath, "utf8"), util.promisify(fs.readFile)(filePath, "utf8"),
this.getFirstValidPath([ this.getFirstValidPath([
{ path: parsedUrl.query.workspace, workspace: true }, { path: parsedUrl.query.workspace, workspace: true },
{ path: parsedUrl.query.folder }, { path: parsedUrl.query.folder, workspace: false },
(await this.readSettings()).lastVisited, (await this.readSettings()).lastVisited,
{ path: this.options.folderUri } { path: this.options.openUri }
]), ]),
this.servicesPromise, this.servicesPromise,
]); ]);
@@ -592,7 +625,9 @@ export class MainServer extends Server {
} }
/** /**
* Choose the first valid path. * Choose the first valid path. If `workspace` is undefined then either a
* workspace or a directory are acceptable. Otherwise it must be a file if a
* workspace or a directory otherwise.
*/ */
private async getFirstValidPath(startPaths: Array<StartPath | undefined>): Promise<{ uri: URI, workspace?: boolean} | undefined> { private async getFirstValidPath(startPaths: Array<StartPath | undefined>): Promise<{ uri: URI, workspace?: boolean} | undefined> {
const logger = this.services.get(ILogService) as ILogService; const logger = this.services.get(ILogService) as ILogService;
@@ -607,9 +642,8 @@ export class MainServer extends Server {
const uri = URI.file(sanitizeFilePath(paths[j], cwd)); const uri = URI.file(sanitizeFilePath(paths[j], cwd));
try { try {
const stat = await util.promisify(fs.stat)(uri.fsPath); const stat = await util.promisify(fs.stat)(uri.fsPath);
// Workspace must be a file. if (typeof startPath.workspace === "undefined" || startPath.workspace !== stat.isDirectory()) {
if (!!startPath.workspace !== stat.isDirectory()) { return { uri, workspace: !stat.isDirectory() };
return { uri, workspace: startPath.workspace };
} }
} catch (error) { } catch (error) {
logger.warn(error.message); logger.warn(error.message);
@@ -719,7 +753,7 @@ export class MainServer extends Server {
if (!environmentService.args["disable-telemetry"]) { if (!environmentService.args["disable-telemetry"]) {
this.services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [{ this.services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [{
appender: combinedAppender( appender: combinedAppender(
new AppInsightsAppender("code-server", null, () => new TelemetryClient(), logService), new AppInsightsAppender("code-server", null, () => new TelemetryClient() as any, logService),
new LogAppender(logService), new LogAppender(logService),
), ),
commonProperties: resolveCommonProperties( commonProperties: resolveCommonProperties(
@@ -750,7 +784,7 @@ export class MainServer extends Server {
this.ipc.registerChannel("request", new RequestChannel(this.services.get(IRequestService) as IRequestService)); this.ipc.registerChannel("request", new RequestChannel(this.services.get(IRequestService) as IRequestService));
this.ipc.registerChannel("telemetry", new TelemetryChannel(telemetryService)); this.ipc.registerChannel("telemetry", new TelemetryChannel(telemetryService));
this.ipc.registerChannel("nodeProxy", new NodeProxyChannel(this.services.get(INodeProxyService) as INodeProxyService)); this.ipc.registerChannel("nodeProxy", new NodeProxyChannel(this.services.get(INodeProxyService) as INodeProxyService));
this.ipc.registerChannel("localizations", createChannelReceiver(this.services.get(ILocalizationsService) as ILocalizationsService)); this.ipc.registerChannel("localizations", <IServerChannel<any>>createChannelReceiver(this.services.get(ILocalizationsService) as ILocalizationsService));
this.ipc.registerChannel("update", new UpdateChannel(instantiationService.createInstance(UpdateService))); this.ipc.registerChannel("update", new UpdateChannel(instantiationService.createInstance(UpdateService)));
this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService)); this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService));
resolve(new ErrorTelemetry(telemetryService)); resolve(new ErrorTelemetry(telemetryService));
@@ -876,4 +910,48 @@ export class MainServer extends Server {
(this.services.get(ILogService) as ILogService).warn(error.message); (this.services.get(ILogService) as ILogService).warn(error.message);
} }
} }
/**
* Return the file path for the heartbeat file.
*/
private get heartbeatPath(): string {
const environment = this.services.get(IEnvironmentService) as IEnvironmentService;
return path.join(environment.userDataPath, "heartbeat");
}
/**
* Return all online connections regardless of type.
*/
private get onlineConnections(): Connection[] {
const online = <Connection[]>[];
this.connections.forEach((connections) => {
connections.forEach((connection) => {
if (typeof connection.offline === "undefined") {
online.push(connection);
}
});
});
return online;
}
/**
* Write to the heartbeat file if we haven't already done so within the
* timeout and start or reset a timer that keeps running as long as there are
* active connections. Failures are logged as warnings.
*/
private heartbeat(): void {
const now = Date.now();
if (now - this.lastHeartbeat >= this.heartbeatInterval) {
util.promisify(fs.writeFile)(this.heartbeatPath, "").catch((error) => {
(this.services.get(ILogService) as ILogService).warn(error.message);
});
this.lastHeartbeat = now;
clearTimeout(this.heartbeatTimer!); // We can clear undefined so ! is fine.
this.heartbeatTimer = setTimeout(() => {
if (this.onlineConnections.length > 0) {
this.heartbeat();
}
}, this.heartbeatInterval);
}
}
} }

View File

@@ -1,9 +1,7 @@
import * as cp from "child_process"; import * as cp from "child_process";
import * as os from "os"; import * as os from "os";
import * as path from "path"; import * as path from "path";
import { Stream } from "stream";
import * as util from "util"; import * as util from "util";
import { toVSBufferReadableStream } from "vs/base/common/buffer";
import { CancellationToken } from "vs/base/common/cancellation"; import { CancellationToken } from "vs/base/common/cancellation";
import { URI } from "vs/base/common/uri"; import { URI } from "vs/base/common/uri";
import * as pfs from "vs/base/node/pfs"; import * as pfs from "vs/base/node/pfs";
@@ -13,12 +11,11 @@ import { IFileService } from "vs/platform/files/common/files";
import { ILogService } from "vs/platform/log/common/log"; import { ILogService } from "vs/platform/log/common/log";
import product from "vs/platform/product/common/product"; import product from "vs/platform/product/common/product";
import { asJson, IRequestService } from "vs/platform/request/common/request"; import { asJson, IRequestService } from "vs/platform/request/common/request";
import { AvailableForDownload, State, UpdateType } from "vs/platform/update/common/update"; import { AvailableForDownload, State, UpdateType, StateType } from "vs/platform/update/common/update";
import { AbstractUpdateService } from "vs/platform/update/electron-main/abstractUpdateService"; import { AbstractUpdateService } from "vs/platform/update/electron-main/abstractUpdateService";
import { ipcMain } from "vs/server/src/node/ipc"; import { ipcMain } from "vs/server/src/node/ipc";
import { extract } from "vs/server/src/node/marketplace"; import { extract } from "vs/server/src/node/marketplace";
import { tmpdir } from "vs/server/src/node/util"; import { tmpdir } from "vs/server/src/node/util";
import * as zlib from "zlib";
interface IUpdate { interface IUpdate {
name: string; name: string;
@@ -37,6 +34,9 @@ export class UpdateService extends AbstractUpdateService {
super(null, configurationService, environmentService, requestService, logService); super(null, configurationService, environmentService, requestService, logService);
} }
/**
* Return true if the currently installed version is the latest.
*/
public async isLatestVersion(latest?: IUpdate | null): Promise<boolean | undefined> { public async isLatestVersion(latest?: IUpdate | null): Promise<boolean | undefined> {
if (!latest) { if (!latest) {
latest = await this.getLatestVersion(); latest = await this.getLatestVersion();
@@ -44,8 +44,12 @@ export class UpdateService extends AbstractUpdateService {
if (latest) { if (latest) {
const latestMajor = parseInt(latest.name); const latestMajor = parseInt(latest.name);
const currentMajor = parseInt(product.codeServerVersion); const currentMajor = parseInt(product.codeServerVersion);
return !isNaN(latestMajor) && !isNaN(currentMajor) && // If these are invalid versions we can't compare meaningfully.
currentMajor <= latestMajor && latest.name === product.codeServerVersion; return isNaN(latestMajor) || isNaN(currentMajor) ||
// This can happen when there is a pre-release for a new major version.
currentMajor > latestMajor ||
// Otherwise assume that if it's not the same then we're out of date.
latest.name === product.codeServerVersion;
} }
return true; return true;
} }
@@ -55,14 +59,16 @@ export class UpdateService extends AbstractUpdateService {
} }
public async doQuitAndInstall(): Promise<void> { public async doQuitAndInstall(): Promise<void> {
ipcMain.relaunch(); if (this.state.type === StateType.Ready) {
ipcMain.relaunch(this.state.update.version);
}
} }
protected async doCheckForUpdates(context: any): Promise<void> { protected async doCheckForUpdates(context: any): Promise<void> {
this.setState(State.CheckingForUpdates(context)); this.setState(State.CheckingForUpdates(context));
try { try {
const update = await this.getLatestVersion(); const update = await this.getLatestVersion();
if (!update || this.isLatestVersion(update)) { if (!update || await this.isLatestVersion(update)) {
this.setState(State.Idle(UpdateType.Archive)); this.setState(State.Idle(UpdateType.Archive));
} else { } else {
this.setState(State.AvailableForDownload({ this.setState(State.AvailableForDownload({
@@ -94,15 +100,7 @@ export class UpdateService extends AbstractUpdateService {
const extractPath = path.join(tmpdir, state.update.version); const extractPath = path.join(tmpdir, state.update.version);
try { try {
await pfs.mkdirp(tmpdir); await pfs.mkdirp(tmpdir);
const context = await this.requestService.request({ url }, CancellationToken.None); const context = await this.requestService.request({ url }, CancellationToken.None, true);
// Decompress the gzip as we download. If the gzip encoding is set then
// the request service already does this.
// HACK: This uses knowledge of the internals of the request service.
if (target !== "darwin" && context.res.headers["content-encoding"] !== "gzip") {
const stream = (context.res as any as Stream);
stream.removeAllListeners();
context.stream = toVSBufferReadableStream(stream.pipe(zlib.createGunzip()));
}
await this.fileService.writeFile(URI.file(downloadPath), context.stream); await this.fileService.writeFile(URI.file(downloadPath), context.stream);
await extract(downloadPath, extractPath, undefined, CancellationToken.None); await extract(downloadPath, extractPath, undefined, CancellationToken.None);
const newBinary = path.join(extractPath, releaseName, "code-server"); const newBinary = path.join(extractPath, releaseName, "code-server");

View File

@@ -4,22 +4,19 @@ module.exports = (remoteAuthority) => {
return { return {
transformIncoming: (uri) => { transformIncoming: (uri) => {
switch (uri.scheme) { switch (uri.scheme) {
case "code-server": return { scheme: "file", path: uri.path }; case "vscode-remote": return { scheme: "file", path: uri.path };
case "file": return { scheme: "code-server", path: uri.path };
default: return uri; default: return uri;
} }
}, },
transformOutgoing: (uri) => { transformOutgoing: (uri) => {
switch (uri.scheme) { switch (uri.scheme) {
case "code-server": return { scheme: "file", path: uri.path }; case "file": return { scheme: "vscode-remote", authority: remoteAuthority, path: uri.path };
case "file": return { scheme: "code-server", authority: remoteAuthority, path: uri.path };
default: return uri; default: return uri;
} }
}, },
transformOutgoingScheme: (scheme) => { transformOutgoingScheme: (scheme) => {
switch (scheme) { switch (scheme) {
case "code-server": return "file"; case "file": return "vscode-remote";
case "file": return "code-server";
default: return scheme; default: return scheme;
} }
}, },

View File

@@ -67,6 +67,10 @@ export const generatePassword = async (length: number = 24): Promise<string> =>
return buffer.toString("hex").substring(0, length); return buffer.toString("hex").substring(0, length);
}; };
export const hash = (str: string): string => {
return crypto.createHash("sha256").update(str).digest("hex");
};
export const getMediaMime = (filePath?: string): string => { export const getMediaMime = (filePath?: string): string => {
return filePath && (vsGetMediaMime(filePath) || (<{[index: string]: string}>{ return filePath && (vsGetMediaMime(filePath) || (<{[index: string]: string}>{
".css": "text/css", ".css": "text/css",

1462
yarn.lock

File diff suppressed because it is too large Load Diff