Compare commits
236 Commits
1.31.0
...
1.1140-vsc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04adf14146 | ||
|
|
406ec0ba71 | ||
|
|
a2ad3d4ff4 | ||
|
|
4a29cd1664 | ||
|
|
0462a93f11 | ||
|
|
db39eacfa1 | ||
|
|
c020cd2f2c | ||
|
|
81bbfa7fbe | ||
|
|
aa1474b675 | ||
|
|
8256252967 | ||
|
|
07342bbee7 | ||
|
|
72152f74ab | ||
|
|
420ca76f54 | ||
|
|
31503fc853 | ||
|
|
cf399ef6ac | ||
|
|
bb5836ec61 | ||
|
|
f36235e03f | ||
|
|
6ef1628acb | ||
|
|
ab8f8a0a22 | ||
|
|
cdb900aca8 | ||
|
|
1622fd4152 | ||
|
|
6c972e855f | ||
|
|
e332882a88 | ||
|
|
d0142e2536 | ||
|
|
e8c8fba91d | ||
|
|
01a63a7241 | ||
|
|
a2e0638c6a | ||
|
|
4e62f938a9 | ||
|
|
4c5bb83fc1 | ||
|
|
a3ac4567e3 | ||
|
|
58cf109a83 | ||
|
|
fab45dedcd | ||
|
|
446573809c | ||
|
|
5ad9398b01 | ||
|
|
bcdbd90197 | ||
|
|
0de7247868 | ||
|
|
c9f91e77cd | ||
|
|
30b8565e2d | ||
|
|
6b887dcc9c | ||
|
|
41c7d98b7b | ||
|
|
b055a26dc3 | ||
|
|
2bc6e1a457 | ||
|
|
e61ea796c6 | ||
|
|
d073622629 | ||
|
|
5f40ebb845 | ||
|
|
c56e2797cc | ||
|
|
166efcb17e | ||
|
|
206e107a9a | ||
|
|
12c8b5d337 | ||
|
|
4aa20fd864 | ||
|
|
0cd4e46055 | ||
|
|
f51823b51f | ||
|
|
55bfeab208 | ||
|
|
309d15cefd | ||
|
|
95006a435a | ||
|
|
4d576ab4d4 | ||
|
|
d5b03ef60e | ||
|
|
cdc5b55a9d | ||
|
|
c3a38e3fea | ||
|
|
cc8c7e2cee | ||
|
|
e0f1787ce6 | ||
|
|
50e6108012 | ||
|
|
630ccfcacc | ||
|
|
c4c26058ef | ||
|
|
8a4da542ae | ||
|
|
e907dbe7e6 | ||
|
|
22b485acd9 | ||
|
|
b8f222acf2 | ||
|
|
aabb2ecda7 | ||
|
|
dfabc070b9 | ||
|
|
da420cdda9 | ||
|
|
c6a46e4753 | ||
|
|
742dd6f597 | ||
|
|
6c3ff1d6f0 | ||
|
|
db57aa229f | ||
|
|
f7342ede69 | ||
|
|
b781ccde9c | ||
|
|
8f62b2bff2 | ||
|
|
7047be859c | ||
|
|
2785e2219a | ||
|
|
4b217fba00 | ||
|
|
3fae68bbab | ||
|
|
a2f20aa25c | ||
|
|
94b8b9a5cf | ||
|
|
bbd8b27fc7 | ||
|
|
6361635b55 | ||
|
|
d2da4cfc43 | ||
|
|
a1136b3a02 | ||
|
|
4dd74b31b9 | ||
|
|
bc0f6eb65d | ||
|
|
6737d3da18 | ||
|
|
eb0f773146 | ||
|
|
ebac84899e | ||
|
|
0b7a090a73 | ||
|
|
a95019f38d | ||
|
|
15948c1af6 | ||
|
|
e73eb74208 | ||
|
|
278c59b920 | ||
|
|
5a1eb858a9 | ||
|
|
3c1dfb1170 | ||
|
|
09a02eb9e9 | ||
|
|
2bd7281fa0 | ||
|
|
e12fcd3a0d | ||
|
|
4af84fcaf6 | ||
|
|
c607015a26 | ||
|
|
217515344e | ||
|
|
dcf409aecb | ||
|
|
2683b7c734 | ||
|
|
3a672d725a | ||
|
|
f484781693 | ||
|
|
97f5b07003 | ||
|
|
7481395353 | ||
|
|
033ef151ca | ||
|
|
3fec7f432c | ||
|
|
4887078423 | ||
|
|
91deaece47 | ||
|
|
03ad2a17b2 | ||
|
|
a4cca6b759 | ||
|
|
6105bba0a4 | ||
|
|
259095eae2 | ||
|
|
38a0706b18 | ||
|
|
c7ae12c2ed | ||
|
|
3331f9b28d | ||
|
|
def4104c53 | ||
|
|
4eb5331ddc | ||
|
|
3bb5c0bbe5 | ||
|
|
83aa952de2 | ||
|
|
e0d33f2399 | ||
|
|
194cbca0f2 | ||
|
|
1697cc32a3 | ||
|
|
f058f90340 | ||
|
|
ca4b0346cb | ||
|
|
8d692ded4a | ||
|
|
dc2253e718 | ||
|
|
d16c6aeb30 | ||
|
|
cdc40d36ff | ||
|
|
80c19878e0 | ||
|
|
18f395b853 | ||
|
|
75435be949 | ||
|
|
ce73bc58e5 | ||
|
|
70219d1071 | ||
|
|
e9e0bf7d84 | ||
|
|
3da1dccf73 | ||
|
|
e02101c676 | ||
|
|
ffc47054dd | ||
|
|
2169045377 | ||
|
|
277c6cb690 | ||
|
|
91a98b8082 | ||
|
|
6028a8b1a8 | ||
|
|
6749f25bbf | ||
|
|
f6b96e3778 | ||
|
|
3b8cd0a3cd | ||
|
|
2f27b5df8c | ||
|
|
400fba7f6f | ||
|
|
bfaadd4e51 | ||
|
|
d16b35ed0b | ||
|
|
633f8dcd72 | ||
|
|
98cad8ae69 | ||
|
|
2e53bb6690 | ||
|
|
e3d9716607 | ||
|
|
862c94401a | ||
|
|
3a6e27bc87 | ||
|
|
ec2d01ab40 | ||
|
|
e4ff723895 | ||
|
|
f9448c6cd4 | ||
|
|
0efae1fcb6 | ||
|
|
7cc7aa51aa | ||
|
|
6c8e513e71 | ||
|
|
f7c1ebf667 | ||
|
|
ba37a34fa2 | ||
|
|
e1dc6967ed | ||
|
|
3155eb76f5 | ||
|
|
e597d49912 | ||
|
|
0a9f5d8eee | ||
|
|
736feaba51 | ||
|
|
307aa4ae7f | ||
|
|
1df352fe26 | ||
|
|
8aff206538 | ||
|
|
03c0bde3a9 | ||
|
|
a36476df21 | ||
|
|
25c46bea32 | ||
|
|
60937c604e | ||
|
|
449d51d24d | ||
|
|
26edea5098 | ||
|
|
8527d10033 | ||
|
|
4387fdfb9e | ||
|
|
41d1be9205 | ||
|
|
76e0338d7f | ||
|
|
f37533579d | ||
|
|
438808573d | ||
|
|
c471babc69 | ||
|
|
d7a66e4f15 | ||
|
|
30d14eeab4 | ||
|
|
e22e2c8b67 | ||
|
|
ffb75b6801 | ||
|
|
cded51f650 | ||
|
|
8bab787804 | ||
|
|
3e9d86ee91 | ||
|
|
e418ecf653 | ||
|
|
36c05ed335 | ||
|
|
5c435a3b6c | ||
|
|
9f4212eace | ||
|
|
96175d36ea | ||
|
|
c8afb7908e | ||
|
|
231cdec7fb | ||
|
|
8e5f288459 | ||
|
|
14f1230bed | ||
|
|
fba3fe5609 | ||
|
|
8e68411174 | ||
|
|
3d654a8df7 | ||
|
|
87d2e22a6b | ||
|
|
17267bd801 | ||
|
|
ac56fcaafc | ||
|
|
e20b79b5cc | ||
|
|
e99f8abc3c | ||
|
|
994531d8bb | ||
|
|
8916cb9bb2 | ||
|
|
d4867ca430 | ||
|
|
3fbdb2e46c | ||
|
|
1d8da2161f | ||
|
|
414eb7076f | ||
|
|
2b3d2933eb | ||
|
|
14ead1a62f | ||
|
|
3b48c57861 | ||
|
|
c772e920cd | ||
|
|
e6aa74c412 | ||
|
|
838c8a6f2c | ||
|
|
4028e33529 | ||
|
|
ec94a92a5f | ||
|
|
379a5243fe | ||
|
|
47765dde23 | ||
|
|
d48d72cb79 | ||
|
|
31518e9754 | ||
|
|
1e0d330778 | ||
|
|
ef6369d62f | ||
|
|
5b0d11e470 |
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Dockerfile
|
||||||
|
# Docs
|
||||||
|
doc/
|
||||||
|
# GitHub stuff
|
||||||
|
.github
|
||||||
|
.gitignore
|
||||||
|
.travis.yml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
node_modules
|
||||||
3
.github/CODEOWNERS
vendored
@@ -1 +1,2 @@
|
|||||||
* @coderasher @kylecarbs
|
* @code-asher @kylecarbs
|
||||||
|
Dockerfile @nhooyr
|
||||||
|
|||||||
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,17 +2,21 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
about: Report problems and unexpected behavior.
|
about: Report problems and unexpected behavior.
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: 'bug'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Please search existing issues to avoid creating duplicates. -->
|
<!-- Please search existing issues to avoid creating duplicates. -->
|
||||||
<!-- All extension-specific issues should be created with the `Extension Bug` template. -->
|
<!-- All extension-specific issues should be created with the `Extension Bug` template. -->
|
||||||
|
|
||||||
- `vscode-remote` version: <!-- The version of vscode-remote -->
|
- `code-server` version: <!-- The version of code-server -->
|
||||||
- OS Version: <!-- OS version, cloud provider, -->
|
- OS Version: <!-- OS version, cloud provider, -->
|
||||||
|
|
||||||
#### Steps to Reproduce
|
## Description
|
||||||
|
|
||||||
1.
|
<!-- Describes the problem here -->
|
||||||
2.
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
|
||||||
|
1. <!-- step 1: click ... -->
|
||||||
|
1. <!-- step 2: ... -->
|
||||||
12
.github/ISSUE_TEMPLATE/extension_bug.md
vendored
@@ -8,11 +8,15 @@ assignees: ''
|
|||||||
|
|
||||||
<!-- Please search existing issues to avoid creating duplicates. -->
|
<!-- Please search existing issues to avoid creating duplicates. -->
|
||||||
|
|
||||||
- `vscode-remote` version: <!-- The version of vscode-remote -->
|
- `code-server` version: <!-- The version of code-server -->
|
||||||
- OS Version: <!-- OS version, cloud provider, -->
|
- OS Version: <!-- OS version, cloud provider, -->
|
||||||
- Extension: <!-- Link to extension -->
|
- Extension: <!-- Link to extension -->
|
||||||
|
|
||||||
#### Steps to Reproduce
|
## Description
|
||||||
|
|
||||||
1.
|
<!-- Describes the problem here -->
|
||||||
2.
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
|
||||||
|
1. <!-- step 1: click ... -->
|
||||||
|
1. <!-- step 2: ... -->
|
||||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
name: Feature Request
|
name: Feature Request
|
||||||
about: Suggest an idea for this project.
|
about: Suggest an idea for this project.
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: 'feature'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
6
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -2,16 +2,16 @@
|
|||||||
name: Question
|
name: Question
|
||||||
about: Ask a question.
|
about: Ask a question.
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: 'question'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Please search existing issues to avoid creating duplicates. -->
|
<!-- Please search existing issues to avoid creating duplicates. -->
|
||||||
|
|
||||||
#### Description
|
## Description
|
||||||
|
|
||||||
<!-- A description of the the question. -->
|
<!-- A description of the the question. -->
|
||||||
|
|
||||||
#### Related Issues
|
## Related Issues
|
||||||
|
|
||||||
<!-- Any issues related to your question. -->
|
<!-- Any issues related to your question. -->
|
||||||
6
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!-- Please answer these questions before submitting your PR. Thanks! -->
|
||||||
|
|
||||||
|
### Describe in detail the problem you had and how this PR fixes it
|
||||||
|
|
||||||
|
### Is there an open issue you can link to?
|
||||||
|
|
||||||
5
.gitignore
vendored
@@ -1,5 +1,8 @@
|
|||||||
lib
|
/lib
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
out
|
out
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
release
|
||||||
|
.vscode
|
||||||
|
.cache
|
||||||
|
|||||||
1
.node-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
10.15.1
|
||||||
65
.travis.yml
@@ -1,33 +1,50 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 8.9.3
|
- 10.15.1
|
||||||
filter_secrets: false
|
services:
|
||||||
|
- docker
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: ubuntu
|
dist: trusty
|
||||||
|
env:
|
||||||
|
- VSCODE_VERSION="1.33.1" MAJOR_VERSION="1" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER-vsc$VSCODE_VERSION" TARGET="centos"
|
||||||
|
- os: linux
|
||||||
|
dist: trusty
|
||||||
|
env:
|
||||||
|
- VSCODE_VERSION="1.33.1" MAJOR_VERSION="1" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER-vsc$VSCODE_VERSION" TARGET="alpine"
|
||||||
- os: osx
|
- os: osx
|
||||||
|
env:
|
||||||
|
- VSCODE_VERSION="1.33.1" MAJOR_VERSION="1" VERSION="$MAJOR_VERSION.$TRAVIS_BUILD_NUMBER-vsc$VSCODE_VERSION"
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install libxkbfile-dev libsecret-1-dev;
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install libxkbfile-dev
|
||||||
fi
|
libsecret-1-dev; fi
|
||||||
|
- npm install -g yarn@1.12.3
|
||||||
script:
|
script:
|
||||||
- scripts/build.sh
|
- scripts/build.sh
|
||||||
# before_deploy:
|
before_deploy:
|
||||||
# - export TRAVIS_TAG="1.0.$TRAVIS_BUILD_NUMBER"
|
- echo "$VERSION" "$TRAVIS_COMMIT"
|
||||||
# - echo "$TRAVIS_TAG" "$TRAVIS_COMMIT"
|
- git config --local user.name "$USER_NAME"
|
||||||
# - git config --local user.name "$USER_NAME"
|
- git config --local user.email "$USER_EMAIL"
|
||||||
# - git config --local user.email "$USER_EMAIL"
|
- git tag "$VERSION" "$TRAVIS_COMMIT"
|
||||||
# - git tag "$TRAVIS_TAG" "$TRAVIS_COMMIT"
|
deploy:
|
||||||
# deploy:
|
provider: releases
|
||||||
# provider: releases
|
file_glob: true
|
||||||
# tag_name: $TRAVIS_TAG
|
draft: true
|
||||||
# target_commitish: $TRAVIS_COMMIT
|
tag_name: "$VERSION"
|
||||||
# name: $TRAVIS_TAG
|
target_commitish: "$TRAVIS_COMMIT"
|
||||||
# skip_cleanup: true
|
name: "$VERSION"
|
||||||
# api_key:
|
skip_cleanup: true
|
||||||
# secure: T/yqCIeqLifteriv8D3CnehNFzSBP309GZqeUfyx8Q+xSQEttA9Enxl+Qw9GkdedYTN4J56iucHIac6CwcvKSKIXqk80CeSEW0BNxZs5wIgv4rRMMy/GAX0NBWKNOkoGlH8M6VyQcM7eY2iGAn1EX755PHalk6rWwfsauRANOQyb2DXQBan5C0YUnogq2qcW1xkIwlXH7l0Ekbtego0f6QPv0rSyOcL1LKm6xk0Aq+xLNKJkT6TSL6xYpkPlZLjnql09Nspkqs6NehWlft2n09bHqAtjNnWw9OYCvxp8mdHeTE5uShuEqYPzdYU5LVFoE7wElI8uqS66noaA18ytZYGw2IrY6GZcn+wtR6WyM2+YXl2HclL1/Fs6Vn8+zwq2IBZchBNv3KJSn1dxiqLlD/s6YQyni17x/9FhtFoNUvsbY5zSC1xrnNQBQWFg0TRnoC9rPR+7hQtT1+5+CvRxpvcNWnPuA22919PFE79ejJulPmsnyF+YLs9c6APJgOpOO1f6fKt5Mcb02dubPqGcQ9NbqUUNTl4IUvEtjG0LnFAgEGerxAcsdnUTxzBVf0LJLlhRKW1BigUTbRwfUJL1DN0mWg9cg7fL5VqrogvNq3uRguxOsYr+bcHDbimQSAY3No3fAkTTqQSJh56Dx57/Un18KxuOTiRB9de1RtiudsI=
|
api_key:
|
||||||
# file_glob: true
|
secure: YL/x24KjYjgYXPcJWk3FV7FGxI79Mh6gBECQEcdlf3fkLEoKFVgzHBoUNWrFPzyR4tgLyWNAgcpD9Lkme1TRWTom7UPjXcwMNyLcLa+uec7ciSAnYD9ntLTpiCuPDD1u0LtRGclSi/EHQ+F8YVq+HZJpXTsJeAmOmihma3GVbGKSZr+BRum+0YZSG4w+o4TOlYzw/4bLWS52MogZcwpjd+hemBbgXLuGU2ziKv2vEKCZFbEeA16II4x1WLI4mutDdCeh7+3aLzGLwDa49NxtsVYNjyNFF75JhCTCNA55e2YMiLz9Uq69IXe/mi5F7xUaFfhIqqLNyKBnKeEOzu3dYnc+8n3LjnQ+00PmkF05nx9kBn3UfV1kwQGh6QbyDmTtBP07rtUMyI14aeQqHjxsaVRdMnwj9Q2DjXRr8UDqESZF0rmK3pHCXS2fBhIzLE8tLVW5Heiba2pQRFMHMZW+KBE97FzcFh7is90Ait3T8enfcd/PWFPYoBejDAdjwxwOkezh5N5ZkYquEfDYuWrFi6zRFCktsruaAcA+xGtTf9oilBBzUqu8Ie+YFWH5me83xakcblJWdaW/D2rLJAJH3m6LFm8lBqyUgDX5t/etob6CpDuYHu5D1J3XINOj/+aLAcadq6qlh70PMZS3zYffUu3JlzaD2amlSHIT8b5YXFc=
|
||||||
# file: packages/server/cli-*
|
file:
|
||||||
# on:
|
- release/*.tar.gz
|
||||||
# repo: codercom/code-server
|
- release/*.zip
|
||||||
# branch: master
|
on:
|
||||||
|
repo: cdr/code-server
|
||||||
|
branch: master
|
||||||
|
cache:
|
||||||
|
yarn: true
|
||||||
|
timeout: 1000
|
||||||
|
directories:
|
||||||
|
- .cache
|
||||||
|
|||||||
53
Dockerfile
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
FROM node:10.15.1
|
||||||
|
|
||||||
|
# Install VS Code's deps. These are the only two it seems we need.
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libxkbfile-dev \
|
||||||
|
libsecret-1-dev
|
||||||
|
|
||||||
|
# Ensure latest yarn.
|
||||||
|
RUN npm install -g yarn@1.13
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# In the future, we can use https://github.com/yarnpkg/rfcs/pull/53 to make yarn use the node_modules
|
||||||
|
# directly which should be fast as it is slow because it populates its own cache every time.
|
||||||
|
RUN yarn && NODE_ENV=production yarn task build:server:binary
|
||||||
|
|
||||||
|
# We deploy with ubuntu so that devs have a familiar environment.
|
||||||
|
FROM ubuntu:18.04
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
openssl \
|
||||||
|
net-tools \
|
||||||
|
git \
|
||||||
|
locales \
|
||||||
|
sudo \
|
||||||
|
dumb-init \
|
||||||
|
vim \
|
||||||
|
curl \
|
||||||
|
wget
|
||||||
|
|
||||||
|
RUN locale-gen en_US.UTF-8
|
||||||
|
# We unfortunately cannot use update-locale because docker will not use the env variables
|
||||||
|
# configured in /etc/default/locale so we need to set it manually.
|
||||||
|
ENV LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
|
RUN adduser --gecos '' --disabled-password coder && \
|
||||||
|
echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
|
||||||
|
|
||||||
|
USER coder
|
||||||
|
# We create first instead of just using WORKDIR as when WORKDIR creates, the user is root.
|
||||||
|
RUN mkdir -p /home/coder/project
|
||||||
|
|
||||||
|
WORKDIR /home/coder/project
|
||||||
|
|
||||||
|
# This assures we have a volume mounted even if the user forgot to do bind mount.
|
||||||
|
# So that they do not lose their data if they delete the container.
|
||||||
|
VOLUME [ "/home/coder/project" ]
|
||||||
|
|
||||||
|
COPY --from=0 /src/packages/server/cli-linux-x64 /usr/local/bin/code-server
|
||||||
|
EXPOSE 8443
|
||||||
|
|
||||||
|
ENTRYPOINT ["dumb-init", "code-server"]
|
||||||
60
README.md
@@ -1,31 +1,52 @@
|
|||||||
# code-server
|
# code-server
|
||||||
|
|
||||||
[](https://github.com/codercom/code-server/issues)
|
[](https://github.com/cdr/code-server/issues)
|
||||||
[](https://github.com/codercom/code-server/releases/latest)
|
[](https://github.com/cdr/code-server/releases/latest)
|
||||||
[](#)
|
[](https://github.com/cdr/code-server/blob/master/LICENSE)
|
||||||
|
[](https://discord.gg/zxSwN8Z)
|
||||||
|
|
||||||
`code-server` is VS Code running on a remote server, accessible through the browser.
|
`code-server` is [VS Code](https://github.com/Microsoft/vscode) running on a remote server, accessible through the browser.
|
||||||
|
|
||||||
|
Try it out:
|
||||||
|
```bash
|
||||||
|
docker run -it -p 127.0.0.1:8443:8443 -v "${PWD}:/home/coder/project" codercom/code-server --allow-http --no-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
- Code on your Chromebook, tablet, and laptop with a consistent dev environment.
|
||||||
|
- If you have a Windows or Mac workstation, more easily develop for Linux.
|
||||||
|
- Take advantage of large cloud servers to speed up tests, compilations, downloads, and more.
|
||||||
|
- Preserve battery life when you're on the go.
|
||||||
|
- All intensive computation runs on your server.
|
||||||
|
- You're no longer running excess instances of Chrome.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
[Try `code-server` now](https://coder.com/signup) for free at coder.com.
|
### Run over SSH
|
||||||
|
|
||||||
1. [Download a binary](https://github.com/codercom/code-server/releases) (Linux and OSX supported. Windows coming soon)
|
Use [sshcode](https://github.com/codercom/sshcode) for a simple setup.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
See docker oneliner mentioned above. Dockerfile is at [/Dockerfile](/Dockerfile).
|
||||||
|
|
||||||
|
### Binaries
|
||||||
|
|
||||||
|
1. [Download a binary](https://github.com/cdr/code-server/releases) (Linux and OS X supported. Windows coming soon)
|
||||||
2. Start the binary with the project directory as the first argument
|
2. Start the binary with the project directory as the first argument
|
||||||
|
|
||||||
```
|
```
|
||||||
code-server <inital directory to open>
|
code-server <initial directory to open>
|
||||||
```
|
```
|
||||||
> You will be prompted to enter the password shown in the CLI
|
> You will be prompted to enter the password shown in the CLI
|
||||||
`code-server` should now be running at https://<IP>:8443.
|
`code-server` should now be running at https://localhost:8443.
|
||||||
|
|
||||||
> code-server uses a self-signed SSL certificate that may prompt your browser to ask you some additional questions before you proceed. Please [read here](doc/self-hosted/index.md) for more information.
|
> code-server uses a self-signed SSL certificate that may prompt your browser to ask you some additional questions before you proceed. Please [read here](doc/self-hosted/index.md) for more information.
|
||||||
|
|
||||||
For detailed instructions and troubleshooting, see the [self-hosted quick start guide](doc/self-hosted/index.md).
|
For detailed instructions and troubleshooting, see the [self-hosted quick start guide](doc/self-hosted/index.md).
|
||||||
|
|
||||||
Quickstart guides for [Google Cloud](doc/admin/install/google_cloud.md), [AWS](doc/admin/install/aws.md), and [Digital Ocean](doc/admin/install/digitalocean.md).
|
Quickstart guides for [Google Cloud](doc/admin/install/google_cloud.md), [AWS](doc/admin/install/aws.md), and [DigitalOcean](doc/admin/install/digitalocean.md).
|
||||||
|
|
||||||
How to [secure your setup](/doc/security/ssl.md).
|
How to [secure your setup](/doc/security/ssl.md).
|
||||||
|
|
||||||
@@ -33,17 +54,28 @@ How to [secure your setup](/doc/security/ssl.md).
|
|||||||
|
|
||||||
### Known Issues
|
### Known Issues
|
||||||
|
|
||||||
- Debugging extensions doesn’t work.
|
- Creating custom VS Code extensions and debugging them doesn't work.
|
||||||
|
|
||||||
### Future
|
### Future
|
||||||
|
- **Stay up to date!** Get notified about new releases of code-server.
|
||||||
|

|
||||||
- Windows support.
|
- Windows support.
|
||||||
- Electron and ChromeOS applications to bridge the gap between local<->remote.
|
- Electron and Chrome OS applications to bridge the gap between local<->remote.
|
||||||
- Run VS Code unit tests against our builds to ensure features work as expected.
|
- Run VS Code unit tests against our builds to ensure features work as expected.
|
||||||
|
|
||||||
|
### Extensions
|
||||||
|
|
||||||
|
At the moment we can't use the official VSCode Marketplace. We've created a custom extension marketplace focused around open-sourced extensions. However, if you have access to the `.vsix` file, you can manually install the extension.
|
||||||
|
|
||||||
|
## Telemetry
|
||||||
|
|
||||||
|
Use the `--disable-telemetry` flag or set `DISABLE_TELEMETRY=true` to disable tracking ENTIRELY.
|
||||||
|
|
||||||
|
We use data collected to improve code-server.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Guides on setup for development will be coming soon. :)
|
Development guides are coming soon.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -51,7 +83,7 @@ Guides on setup for development will be coming soon. :)
|
|||||||
|
|
||||||
## Enterprise
|
## Enterprise
|
||||||
|
|
||||||
Visit [our enterprise page](https://coder.com/enterprise) for more information on our enterprise offering.
|
Visit [our enterprise page](https://coder.com/enterprise) for more information about our enterprise offering.
|
||||||
|
|
||||||
## Commercialization
|
## Commercialization
|
||||||
|
|
||||||
|
|||||||
42
build/platform.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Script that detects platform name and arch.
|
||||||
|
* Cannot use os.platform() as that won't detect libc version
|
||||||
|
*/
|
||||||
|
import * as cp from "child_process";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as os from "os";
|
||||||
|
|
||||||
|
enum Lib {
|
||||||
|
GLIBC,
|
||||||
|
MUSL,
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLIB: Lib | undefined = ((): Lib | undefined => {
|
||||||
|
if (os.platform() !== "linux") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const glibc = cp.spawnSync("getconf", ["GNU_LIBC_VERSION"]);
|
||||||
|
if (glibc.status === 0) {
|
||||||
|
return Lib.GLIBC;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ldd = cp.spawnSync("ldd", ["--version"]);
|
||||||
|
if (ldd.stdout && ldd.stdout.indexOf("musl") !== -1) {
|
||||||
|
return Lib.MUSL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const muslFile = fs.readdirSync("/lib").find((value) => value.startsWith("libc.musl"));
|
||||||
|
if (muslFile) {
|
||||||
|
return Lib.MUSL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Lib.GLIBC;
|
||||||
|
})();
|
||||||
|
|
||||||
|
export const platform = (): NodeJS.Platform | "musl" => {
|
||||||
|
if (CLIB === Lib.MUSL) {
|
||||||
|
return "musl";
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.platform();
|
||||||
|
};
|
||||||
204
build/tasks.ts
@@ -1,23 +1,34 @@
|
|||||||
import { register, run } from "@coder/runner";
|
import { register, run } from "@coder/runner";
|
||||||
|
import { logger, field } from "@coder/logger";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as fse from "fs-extra";
|
import * as fse from "fs-extra";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
|
import { platform } from "./platform";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as zlib from "zlib";
|
import * as zlib from "zlib";
|
||||||
|
import * as https from "https";
|
||||||
|
import * as tar from "tar";
|
||||||
|
|
||||||
const isWin = os.platform() === "win32";
|
const isWin = os.platform() === "win32";
|
||||||
const libPath = path.join(__dirname, "../lib");
|
const libPath = path.join(__dirname, "../lib");
|
||||||
const vscodePath = path.join(libPath, "vscode");
|
const vscodePath = path.join(libPath, "vscode");
|
||||||
|
const defaultExtensionsPath = path.join(libPath, "extensions");
|
||||||
const pkgsPath = path.join(__dirname, "../packages");
|
const pkgsPath = path.join(__dirname, "../packages");
|
||||||
const defaultExtensionsPath = path.join(libPath, "VSCode-linux-x64/resources/app/extensions");
|
const vscodeVersion = process.env.VSCODE_VERSION || "1.33.1";
|
||||||
|
const vsSourceUrl = `https://codesrv-ci.cdr.sh/vstar-${vscodeVersion}.tar.gz`;
|
||||||
|
|
||||||
const buildServerBinary = register("build:server:binary", async (runner) => {
|
const buildServerBinary = register("build:server:binary", async (runner) => {
|
||||||
|
logger.info("Building with environment", field("env", {
|
||||||
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
|
VERSION: process.env.VERSION,
|
||||||
|
OSTYPE: process.env.OSTYPE,
|
||||||
|
TARGET: process.env.TARGET,
|
||||||
|
}));
|
||||||
|
|
||||||
await ensureInstalled();
|
await ensureInstalled();
|
||||||
await copyForDefaultExtensions();
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
buildBootstrapFork(),
|
buildBootstrapFork(),
|
||||||
buildWeb(),
|
buildWeb(),
|
||||||
buildDefaultExtensions(),
|
|
||||||
buildServerBundle(),
|
buildServerBundle(),
|
||||||
buildAppBrowser(),
|
buildAppBrowser(),
|
||||||
]);
|
]);
|
||||||
@@ -32,48 +43,12 @@ const buildServerBinaryPackage = register("build:server:binary:package", async (
|
|||||||
throw new Error("Cannot build binary without server bundle built");
|
throw new Error("Cannot build binary without server bundle built");
|
||||||
}
|
}
|
||||||
await buildServerBinaryCopy();
|
await buildServerBinaryCopy();
|
||||||
await dependencyNexeBinary();
|
const resp = await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build:binary"]);
|
||||||
const resp = await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build:nexe"]);
|
|
||||||
if (resp.exitCode !== 0) {
|
if (resp.exitCode !== 0) {
|
||||||
throw new Error(`Failed to package binary: ${resp.stderr}`);
|
throw new Error(`Failed to package binary: ${resp.stderr}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const dependencyNexeBinary = register("dependency:nexe", async (runner) => {
|
|
||||||
if (os.platform() === "linux") {
|
|
||||||
const nexeDir = path.join(os.homedir(), ".nexe");
|
|
||||||
const targetBinaryName = `${os.platform()}-${os.arch()}-${process.version.substr(1)}`;
|
|
||||||
const targetBinaryPath = path.join(nexeDir, targetBinaryName);
|
|
||||||
if (!fs.existsSync(targetBinaryPath)) {
|
|
||||||
/**
|
|
||||||
* We create a binary with nexe
|
|
||||||
* so we can compress it
|
|
||||||
*/
|
|
||||||
fse.mkdirpSync(nexeDir);
|
|
||||||
runner.cwd = nexeDir;
|
|
||||||
await runner.execute("wget", [`https://github.com/nexe/nexe/releases/download/v3.0.0-beta.15/${targetBinaryName}`]);
|
|
||||||
await runner.execute("chmod", ["+x", targetBinaryPath]);
|
|
||||||
}
|
|
||||||
if (fs.statSync(targetBinaryPath).size >= 20000000) {
|
|
||||||
// Compress w/ upx
|
|
||||||
const upxFolder = path.join(os.tmpdir(), "upx");
|
|
||||||
const upxBinary = path.join(upxFolder, "upx");
|
|
||||||
if (!fs.existsSync(upxBinary)) {
|
|
||||||
fse.mkdirpSync(upxFolder);
|
|
||||||
runner.cwd = upxFolder;
|
|
||||||
const upxExtract = await runner.execute("bash", ["-c", "curl -L https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz | tar xJ --strip-components=1"]);
|
|
||||||
if (upxExtract.exitCode !== 0) {
|
|
||||||
throw new Error(`Failed to extract upx: ${upxExtract.stderr}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(upxBinary)) {
|
|
||||||
throw new Error("Not sure how, but the UPX binary does not exist");
|
|
||||||
}
|
|
||||||
await runner.execute(upxBinary, [targetBinaryPath]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const buildServerBinaryCopy = register("build:server:binary:copy", async (runner) => {
|
const buildServerBinaryCopy = register("build:server:binary:copy", async (runner) => {
|
||||||
const cliPath = path.join(pkgsPath, "server");
|
const cliPath = path.join(pkgsPath, "server");
|
||||||
const cliBuildPath = path.join(cliPath, "build");
|
const cliBuildPath = path.join(cliPath, "build");
|
||||||
@@ -82,19 +57,11 @@ const buildServerBinaryCopy = register("build:server:binary:copy", async (runner
|
|||||||
const bootstrapForkPath = path.join(pkgsPath, "vscode", "out", "bootstrap-fork.js");
|
const bootstrapForkPath = path.join(pkgsPath, "vscode", "out", "bootstrap-fork.js");
|
||||||
const webOutputPath = path.join(pkgsPath, "web", "out");
|
const webOutputPath = path.join(pkgsPath, "web", "out");
|
||||||
const browserAppOutputPath = path.join(pkgsPath, "app", "browser", "out");
|
const browserAppOutputPath = path.join(pkgsPath, "app", "browser", "out");
|
||||||
const nodePtyModule = path.join(pkgsPath, "protocol", "node_modules", "node-pty-prebuilt", "build", "Release", "pty.node");
|
|
||||||
const spdlogModule = path.join(pkgsPath, "protocol", "node_modules", "spdlog", "build", "Release", "spdlog.node");
|
|
||||||
let ripgrepPath = path.join(pkgsPath, "..", "lib", "vscode", "node_modules", "vscode-ripgrep", "bin", "rg");
|
let ripgrepPath = path.join(pkgsPath, "..", "lib", "vscode", "node_modules", "vscode-ripgrep", "bin", "rg");
|
||||||
if (isWin) {
|
if (isWin) {
|
||||||
ripgrepPath += ".exe";
|
ripgrepPath += ".exe";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(nodePtyModule)) {
|
|
||||||
throw new Error("Could not find pty.node. Ensure all packages have been installed");
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(spdlogModule)) {
|
|
||||||
throw new Error("Could not find spdlog.node. Ensure all packages have been installed");
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(webOutputPath)) {
|
if (!fs.existsSync(webOutputPath)) {
|
||||||
throw new Error("Web bundle must be built");
|
throw new Error("Web bundle must be built");
|
||||||
}
|
}
|
||||||
@@ -109,24 +76,22 @@ const buildServerBinaryCopy = register("build:server:binary:copy", async (runner
|
|||||||
}
|
}
|
||||||
fse.copySync(defaultExtensionsPath, path.join(cliBuildPath, "extensions"));
|
fse.copySync(defaultExtensionsPath, path.join(cliBuildPath, "extensions"));
|
||||||
fs.writeFileSync(path.join(cliBuildPath, "bootstrap-fork.js.gz"), zlib.gzipSync(fs.readFileSync(bootstrapForkPath)));
|
fs.writeFileSync(path.join(cliBuildPath, "bootstrap-fork.js.gz"), zlib.gzipSync(fs.readFileSync(bootstrapForkPath)));
|
||||||
const cpDir = (dir: string, subdir: "auth" | "unauth", rootPath: string): void => {
|
const cpDir = (dir: string, rootPath: string, subdir?: "login"): void => {
|
||||||
const stat = fs.statSync(dir);
|
const stat = fs.statSync(dir);
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
const paths = fs.readdirSync(dir);
|
const paths = fs.readdirSync(dir);
|
||||||
paths.forEach((p) => cpDir(path.join(dir, p), subdir, rootPath));
|
paths.forEach((p) => cpDir(path.join(dir, p), rootPath, subdir));
|
||||||
} else if (stat.isFile()) {
|
} else if (stat.isFile()) {
|
||||||
const newPath = path.join(cliBuildPath, "web", subdir, path.relative(rootPath, dir));
|
const newPath = path.join(cliBuildPath, "web", subdir || "", path.relative(rootPath, dir));
|
||||||
fse.mkdirpSync(path.dirname(newPath));
|
fse.mkdirpSync(path.dirname(newPath));
|
||||||
fs.writeFileSync(newPath + ".gz", zlib.gzipSync(fs.readFileSync(dir)));
|
fs.writeFileSync(newPath + ".gz", zlib.gzipSync(fs.readFileSync(dir)));
|
||||||
} else {
|
} else {
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpDir(webOutputPath, "auth", webOutputPath);
|
cpDir(webOutputPath, webOutputPath);
|
||||||
cpDir(browserAppOutputPath, "unauth", browserAppOutputPath);
|
cpDir(browserAppOutputPath, browserAppOutputPath, "login");
|
||||||
fse.mkdirpSync(path.join(cliBuildPath, "dependencies"));
|
fse.mkdirpSync(path.join(cliBuildPath, "dependencies"));
|
||||||
fse.copySync(nodePtyModule, path.join(cliBuildPath, "dependencies", "pty.node"));
|
|
||||||
fse.copySync(spdlogModule, path.join(cliBuildPath, "dependencies", "spdlog.node"));
|
|
||||||
fse.copySync(ripgrepPath, path.join(cliBuildPath, "dependencies", "rg"));
|
fse.copySync(ripgrepPath, path.join(cliBuildPath, "dependencies", "rg"));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,93 +129,50 @@ const buildWeb = register("build:web", async (runner) => {
|
|||||||
await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build"]);
|
await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const extDirPath = path.join("lib", "vscode-default-extensions");
|
|
||||||
const copyForDefaultExtensions = register("build:copy-vscode", async (runner) => {
|
|
||||||
if (!fs.existsSync(defaultExtensionsPath)) {
|
|
||||||
await ensureClean();
|
|
||||||
await ensureInstalled();
|
|
||||||
await new Promise((resolve, reject): void => {
|
|
||||||
fse.remove(extDirPath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await new Promise((resolve, reject): void => {
|
|
||||||
fse.copy(vscodePath, extDirPath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const buildDefaultExtensions = register("build:default-extensions", async (runner) => {
|
|
||||||
if (!fs.existsSync(defaultExtensionsPath)) {
|
|
||||||
await copyForDefaultExtensions();
|
|
||||||
runner.cwd = extDirPath;
|
|
||||||
const resp = await runner.execute(isWin ? "npx.cmd" : "npx", [isWin ? "gulp.cmd" : "gulp", "vscode-linux-x64"]);
|
|
||||||
if (resp.exitCode !== 0) {
|
|
||||||
throw new Error(`Failed to build default extensions: ${resp.stderr}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ensureInstalled = register("vscode:install", async (runner) => {
|
const ensureInstalled = register("vscode:install", async (runner) => {
|
||||||
await ensureCloned();
|
runner.cwd = libPath;
|
||||||
|
|
||||||
runner.cwd = vscodePath;
|
if (fs.existsSync(vscodePath) && fs.existsSync(defaultExtensionsPath)) {
|
||||||
const install = await runner.execute(isWin ? "yarn.cmd" : "yarn", []);
|
const pkgVersion = JSON.parse(fs.readFileSync(path.join(vscodePath, "package.json")).toString("utf8")).version;
|
||||||
if (install.exitCode !== 0) {
|
if (pkgVersion === vscodeVersion) {
|
||||||
throw new Error(`Failed to install vscode dependencies: ${install.stderr}`);
|
runner.cwd = vscodePath;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ensureCloned = register("vscode:clone", async (runner) => {
|
const reset = await runner.execute("git", ["reset", "--hard"]);
|
||||||
if (fs.existsSync(vscodePath)) {
|
if (reset.exitCode !== 0) {
|
||||||
await ensureClean();
|
throw new Error(`Failed to clean git repository: ${reset.stderr}`);
|
||||||
} else {
|
}
|
||||||
fse.mkdirpSync(libPath);
|
|
||||||
runner.cwd = libPath;
|
return;
|
||||||
const clone = await runner.execute("git", ["clone", "https://github.com/microsoft/vscode"]);
|
|
||||||
if (clone.exitCode !== 0) {
|
|
||||||
throw new Error(`Failed to clone: ${clone.exitCode}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runner.cwd = vscodePath;
|
fse.removeSync(libPath);
|
||||||
const checkout = await runner.execute("git", ["checkout", "tags/1.31.0"]);
|
fse.mkdirpSync(libPath);
|
||||||
if (checkout.exitCode !== 0) {
|
|
||||||
throw new Error(`Failed to checkout: ${checkout.stderr}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ensureClean = register("vscode:clean", async (runner) => {
|
await new Promise<void>((resolve, reject): void => {
|
||||||
runner.cwd = vscodePath;
|
https.get(vsSourceUrl, (res) => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
return reject(res.statusMessage);
|
||||||
|
}
|
||||||
|
|
||||||
const status = await runner.execute("git", ["status", "--porcelain"]);
|
res.pipe(tar.x({
|
||||||
if (status.stdout.trim() !== "") {
|
C: libPath,
|
||||||
const clean = await runner.execute("git", ["clean", "-f", "-d", "-X"]);
|
}).on("finish", () => {
|
||||||
if (clean.exitCode !== 0) {
|
resolve();
|
||||||
throw new Error(`Failed to clean git repository: ${clean.stderr}`);
|
}).on("error", (err: Error) => {
|
||||||
}
|
reject(err);
|
||||||
const removeUnstaged = await runner.execute("git", ["checkout", "--", "."]);
|
}));
|
||||||
if (removeUnstaged.exitCode !== 0) {
|
}).on("error", (err) => {
|
||||||
throw new Error(`Failed to remove unstaged files: ${removeUnstaged.stderr}`);
|
reject(err);
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const ensurePatched = register("vscode:patch", async (runner) => {
|
const ensurePatched = register("vscode:patch", async (runner) => {
|
||||||
if (!fs.existsSync(vscodePath)) {
|
if (!fs.existsSync(vscodePath)) {
|
||||||
throw new Error("vscode must be cloned to patch");
|
throw new Error("vscode must be cloned to patch");
|
||||||
}
|
}
|
||||||
await ensureClean();
|
await ensureInstalled();
|
||||||
|
|
||||||
runner.cwd = vscodePath;
|
runner.cwd = vscodePath;
|
||||||
const patchPath = path.join(__dirname, "../scripts/vscode.patch");
|
const patchPath = path.join(__dirname, "../scripts/vscode.patch");
|
||||||
@@ -260,4 +182,30 @@ const ensurePatched = register("vscode:patch", async (runner) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register("package", async (runner, releaseTag) => {
|
||||||
|
if (!releaseTag) {
|
||||||
|
throw new Error("Please specify the release tag.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const releasePath = path.resolve(__dirname, "../release");
|
||||||
|
|
||||||
|
const archiveName = `code-server${releaseTag}-${platform()}-${os.arch()}`;
|
||||||
|
const archiveDir = path.join(releasePath, archiveName);
|
||||||
|
fse.removeSync(archiveDir);
|
||||||
|
fse.mkdirpSync(archiveDir);
|
||||||
|
|
||||||
|
const binaryPath = path.join(__dirname, `../packages/server/cli-${platform()}-${os.arch()}`);
|
||||||
|
const binaryDestination = path.join(archiveDir, "code-server");
|
||||||
|
fse.copySync(binaryPath, binaryDestination);
|
||||||
|
fs.chmodSync(binaryDestination, "755");
|
||||||
|
["README.md", "LICENSE"].forEach((fileName) => {
|
||||||
|
fse.copySync(path.resolve(__dirname, `../${fileName}`), path.join(archiveDir, fileName));
|
||||||
|
});
|
||||||
|
|
||||||
|
runner.cwd = releasePath;
|
||||||
|
await (os.platform() === "linux"
|
||||||
|
? runner.execute("tar", ["-cvzf", `${archiveName}.tar.gz`, `${archiveName}`])
|
||||||
|
: runner.execute("zip", ["-r", `${archiveName}.zip`, `${archiveName}`]));
|
||||||
|
});
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
|||||||
74
deployment/aws/deployment.yaml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: code-server
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: code-server
|
||||||
|
namespace: code-server
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 8443
|
||||||
|
name: https
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app: code-server
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
kind: StorageClass
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: gp2
|
||||||
|
annotations:
|
||||||
|
storageclass.kubernetes.io/is-default-class: "true"
|
||||||
|
provisioner: kubernetes.io/aws-ebs
|
||||||
|
parameters:
|
||||||
|
type: gp2
|
||||||
|
fsType: ext4
|
||||||
|
---
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: code-store
|
||||||
|
namespace: code-server
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 60Gi
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: code-server
|
||||||
|
name: code-server
|
||||||
|
namespace: code-server
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: code-server
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: code-server
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: codercom/code-server
|
||||||
|
imagePullPolicy: Always
|
||||||
|
name: code-servery
|
||||||
|
ports:
|
||||||
|
- containerPort: 8443
|
||||||
|
name: https
|
||||||
|
volumeMounts:
|
||||||
|
- name: code-server-storage
|
||||||
|
mountPath: /go/src
|
||||||
|
volumes:
|
||||||
|
- name: code-server-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: code-store
|
||||||
|
|
||||||
43
deployment/deployment.yaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: code-server
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: code-server
|
||||||
|
namespace: code-server
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 8443
|
||||||
|
name: https
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app: code-server
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: code-server
|
||||||
|
name: code-server
|
||||||
|
namespace: code-server
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: code-server
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: code-server
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: codercom/code-server
|
||||||
|
imagePullPolicy: Always
|
||||||
|
name: code-server
|
||||||
|
ports:
|
||||||
|
- containerPort: 8443
|
||||||
|
name: https
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This tutorial shows you how to deploy `code-server` on an EC2 AWS instance.
|
This tutorial shows you how to deploy `code-server` on an EC2 AWS instance.
|
||||||
|
|
||||||
If you're just starting out, we recommend [installing code-server locally](../../self-hosted/index.md). It takes only a few minutes and lets you try out all of the features. You can also try out the IDE on a container hosted [by Coder](http://coder.com/signup)
|
If you're just starting out, we recommend [installing code-server locally](../../self-hosted/index.md). It takes only a few minutes and lets you try out all of the features.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -28,23 +28,35 @@ If you're just starting out, we recommend [installing code-server locally](../..
|
|||||||
- In the description of your EC2 instance copy the public DNS (iPv4) address using the copy to clipboard button
|
- In the description of your EC2 instance copy the public DNS (iPv4) address using the copy to clipboard button
|
||||||
- Open a terminal on your computer and use the following command to SSH into your EC2 instance
|
- Open a terminal on your computer and use the following command to SSH into your EC2 instance
|
||||||
```
|
```
|
||||||
ssh i "path/to/your/keypair.pem" ubuntu@(paste the public DNS here)
|
ssh -i "path/to/your/keypair.pem" ubuntu@(paste the public DNS here)
|
||||||
```
|
```
|
||||||
>example: `ssh -i "/Users/John/Downloads/TestInstance.pem" ubuntu@ec2-3-45-678-910.compute-1.amazonaws.co`
|
>example: `ssh -i "/Users/John/Downloads/TestInstance.pem" ubuntu@ec2-3-45-678-910.compute-1.amazonaws.co`
|
||||||
- You should see a prompt for your EC2 instance like so<img src="../../assets/aws_ubuntu.png">
|
- You should see a prompt for your EC2 instance like so<img src="../../assets/aws_ubuntu.png">
|
||||||
- At this point it is time to download the `code-server` binary. We will of course want the linux version. Make sure you copy the link for the latest linux version on our [releases page](https://github.com/codercom/code-server/releases)
|
- At this point it is time to download the `code-server` binary. We will of course want the linux version.
|
||||||
- With the URL in the clipboard, run:
|
- Find the latest Linux release from this URL:
|
||||||
```
|
```
|
||||||
wget https://github.com/codercom/code-server/releases/download/0.1.4/code-server-linux
|
https://github.com/cdr/code-server/releases/latest
|
||||||
|
```
|
||||||
|
- Replace {version} in the following command with the version found on the releases page and run it (or just copy the download URL from the releases page):
|
||||||
|
```
|
||||||
|
wget https://github.com/cdr/code-server/releases/download/{version}/code-server-{version}-linux-x64.tar.gz
|
||||||
|
```
|
||||||
|
- Extract the downloaded tar.gz file with this command, for example:
|
||||||
|
```
|
||||||
|
tar -xvzf code-server-{version}-linux-x64.tar.gz
|
||||||
|
```
|
||||||
|
- Navigate to extracted directory with this command:
|
||||||
|
```
|
||||||
|
cd code-server-{version}-linux-x64
|
||||||
```
|
```
|
||||||
- If you run into any permission errors, make the binary executable by running:
|
- If you run into any permission errors, make the binary executable by running:
|
||||||
```
|
```
|
||||||
chmod +x code-server-linux
|
chmod +x code-server
|
||||||
```
|
```
|
||||||
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../security/ssl.md)
|
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../../security/ssl.md)
|
||||||
- Finally, run
|
- Finally, run
|
||||||
```
|
```
|
||||||
sudo ./code-server-linux -p 80
|
sudo ./code-server -p 80
|
||||||
```
|
```
|
||||||
- When you visit the public IP for your AWS instance, you will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"**<img src ="../../assets/chrome_warning.png">
|
- When you visit the public IP for your AWS instance, you will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"**<img src ="../../assets/chrome_warning.png">
|
||||||
- Then click **"proceed anyway"**<img src="../../assets/chrome_confirm.png">
|
- Then click **"proceed anyway"**<img src="../../assets/chrome_confirm.png">
|
||||||
@@ -54,4 +66,4 @@ If you're just starting out, we recommend [installing code-server locally](../..
|
|||||||
> The `-p 80` flag is necessary in order to make the IDE accessible from the public IP of your instance (also available from the description in the instances page.
|
> The `-p 80` flag is necessary in order to make the IDE accessible from the public IP of your instance (also available from the description in the instances page.
|
||||||
|
|
||||||
---
|
---
|
||||||
> NOTE: If you get stuck or need help, [file an issue](https://github.com/codercom/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide).
|
> NOTE: If you get stuck or need help, [file an issue](https://github.com/cdr/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This tutorial shows you how to deploy `code-server` to a single node running on DigitalOcean.
|
This tutorial shows you how to deploy `code-server` to a single node running on DigitalOcean.
|
||||||
|
|
||||||
If you're just starting out, we recommend [installing code-server locally](../../self-hosted/index.md). It takes only a few minutes and lets you try out all of the features. You can also try out the IDE on a container hosted [by Coder](http://coder.com/signup)
|
If you're just starting out, we recommend [installing code-server locally](../../self-hosted/index.md). It takes only a few minutes and lets you try out all of the features.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -15,23 +15,35 @@ If you're just starting out, we recommend [installing code-server locally](../..
|
|||||||
- Launch your instance
|
- Launch your instance
|
||||||
- Open a terminal on your computer and SSH into your instance
|
- Open a terminal on your computer and SSH into your instance
|
||||||
> example: ssh root@203.0.113.0
|
> example: ssh root@203.0.113.0
|
||||||
- Once in the SSH session, visit code-server [releases page](https://github.com/codercom/code-server/releases/) and copy the link to the download for the latest linux release
|
- Once in the SSH session, visit code-server [releases page](https://github.com/cdr/code-server/releases/) and copy the link to the download for the latest linux release
|
||||||
- In the shell run the below command with the URL from your clipboard
|
- Find the latest Linux release from this URL:
|
||||||
```
|
```
|
||||||
wget https://github.com/codercom/code-server/releases/download/0.1.4/code-server-linux
|
https://github.com/cdr/code-server/releases/latest
|
||||||
|
```
|
||||||
|
- Replace {version} in the following command with the version found on the releases page and run it (or just copy the download URL from the releases page):
|
||||||
|
```
|
||||||
|
wget https://github.com/cdr/code-server/releases/download/{version}/code-server-{version}-linux-x64.tar.gz
|
||||||
|
```
|
||||||
|
- Extract the downloaded tar.gz file with this command, for example:
|
||||||
|
```
|
||||||
|
tar -xvzf code-server-{version}-linux-x64.tar.gz
|
||||||
|
```
|
||||||
|
- Navigate to extracted directory with this command:
|
||||||
|
```
|
||||||
|
cd code-server-{version}-linux-x64
|
||||||
```
|
```
|
||||||
- If you run into any permission errors when attempting to run the binary:
|
- If you run into any permission errors when attempting to run the binary:
|
||||||
```
|
```
|
||||||
chmod +x code-server-linux
|
chmod +x code-server
|
||||||
```
|
```
|
||||||
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../security/ssl.md)
|
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../../security/ssl.md)
|
||||||
- Finally start the code-server
|
- Finally start the code-server
|
||||||
```
|
```
|
||||||
sudo ./code-server-linux -p80
|
sudo ./code-server -p 80
|
||||||
```
|
```
|
||||||
> For instructions on how to keep the server running after you end your SSH session please checkout [how to use systemd](https://www.linode.com/docs/quick-answers/linux/start-service-at-boot/) to start linux based services if they are killed
|
> For instructions on how to keep the server running after you end your SSH session please checkout [how to use systemd](https://www.linode.com/docs/quick-answers/linux/start-service-at-boot/) to start linux based services if they are killed
|
||||||
- When you visit the public IP for your Digital Ocean instance, you will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"**<img src ="../../assets/chrome_warning.png">
|
- When you visit the public IP for your Digital Ocean instance, you will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"**<img src ="../../assets/chrome_warning.png">
|
||||||
- Then click **"proceed anyway"**<img src="../../assets/chrome_confirm.png">
|
- Then click **"proceed anyway"**<img src="../../assets/chrome_confirm.png">
|
||||||
|
|
||||||
---
|
---
|
||||||
> NOTE: If you get stuck or need help, [file an issue](https://github.com/codercom/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide).
|
> NOTE: If you get stuck or need help, [file an issue](https://github.com/cdr/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This tutorial shows you how to deploy `code-server` to a single node running on Google Cloud.
|
This tutorial shows you how to deploy `code-server` to a single node running on Google Cloud.
|
||||||
|
|
||||||
If you're just starting out, we recommend [installing code-server locally](../../self-hosted/index.md). It takes only a few minutes and lets you try out all of the features. You can also try out the IDE on a container hosted [by Coder](http://coder.com/signup)
|
If you're just starting out, we recommend [installing code-server locally](../../self-hosted/index.md). It takes only a few minutes and lets you try out all of the features.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,34 +13,59 @@ If you're just starting out, we recommend [installing code-server locally](../..
|
|||||||
- Choose an appropriate machine type (we recommend 2 vCPU and 7.5 GB RAM, more depending on team size and number of repositories/languages enabled)
|
- Choose an appropriate machine type (we recommend 2 vCPU and 7.5 GB RAM, more depending on team size and number of repositories/languages enabled)
|
||||||
- Choose Ubuntu 16.04 LTS as your boot disk
|
- Choose Ubuntu 16.04 LTS as your boot disk
|
||||||
- Check the boxes for **Allow HTTP traffic** and **Allow HTTPS traffic** in the **Firewall** section
|
- Check the boxes for **Allow HTTP traffic** and **Allow HTTPS traffic** in the **Firewall** section
|
||||||
- Create your VM, and **take note** of it's public IP address.
|
- Create your VM, and **take note** of its public IP address.
|
||||||
- Copy the link to download the latest Linux binary from our [releases page](https://github.com/codercom/code-server/releases)
|
- Copy the link to download the latest Linux binary from our [releases page](https://github.com/cdr/code-server/releases)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Final Steps
|
## Final Steps
|
||||||
|
|
||||||
1. SSH into your Google Cloud VM
|
- SSH into your Google Cloud VM
|
||||||
```
|
```
|
||||||
gcloud compute ssh --zone [region] [instance name]
|
gcloud compute ssh --zone [region] [instance name]
|
||||||
```
|
```
|
||||||
2. Download the binary using the link we copied to clipboard
|
|
||||||
|
- Find the latest Linux release from this URL:
|
||||||
```
|
```
|
||||||
wget https://github.com/codercom/code-server/releases/download/0.1.4/code-server-linux
|
https://github.com/cdr/code-server/releases/latest
|
||||||
```
|
```
|
||||||
3. Make the binary executable if you run into any errors regarding permission:
|
|
||||||
```
|
- Replace {version} in the following command with the version found on the releases page and run it (or just copy the download URL from the releases page):
|
||||||
chmod +x code-server-linux
|
|
||||||
```
|
|
||||||
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../security/ssl.md)
|
|
||||||
4. Start the code-server
|
|
||||||
```
|
```
|
||||||
sudo ./code-server-linux -p 80
|
wget https://github.com/cdr/code-server/releases/download/{version}/code-server-{version}-linux-x64.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Extract the downloaded tar.gz file with this command, for example:
|
||||||
|
```
|
||||||
|
tar -xvzf code-server-{version}-linux-x64.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
- Navigate to extracted directory with this command:
|
||||||
|
```
|
||||||
|
cd code-server-{version}-linux-x64
|
||||||
|
```
|
||||||
|
|
||||||
|
- Make the binary executable if you run into any errors regarding permission:
|
||||||
|
```
|
||||||
|
chmod +x code-server
|
||||||
|
```
|
||||||
|
|
||||||
|
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../../security/ssl.md)
|
||||||
|
|
||||||
|
- Start the code-server
|
||||||
|
```
|
||||||
|
sudo ./code-server -p 80
|
||||||
|
```
|
||||||
|
|
||||||
> For instructions on how to keep the server running after you end your SSH session please checkout [how to use systemd](https://www.linode.com/docs/quick-answers/linux/start-service-at-boot/) to start linux based services if they are killed
|
> For instructions on how to keep the server running after you end your SSH session please checkout [how to use systemd](https://www.linode.com/docs/quick-answers/linux/start-service-at-boot/) to start linux based services if they are killed
|
||||||
5. Access code-server from the public IP of your Google Cloud instance we noted earlier in your browser.
|
|
||||||
|
- Access code-server from the public IP of your Google Cloud instance we noted earlier in your browser.
|
||||||
> example: 32.32.32.234
|
> example: 32.32.32.234
|
||||||
6. You will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"**<img src ="../../assets/chrome_warning.png">
|
|
||||||
7. Then click **"proceed anyway"**<img src="../../assets/chrome_confirm.png">
|
- You will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"**<img src ="../../assets/chrome_warning.png">
|
||||||
|
|
||||||
|
- Then click **"proceed anyway"**<img src="../../assets/chrome_confirm.png">
|
||||||
|
|
||||||
---
|
---
|
||||||
> NOTE: If you get stuck or need help, [file an issue](https://github.com/codercom/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide).
|
|
||||||
|
> NOTE: If you get stuck or need help, [file an issue](https://github.com/cdr/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide).
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 137 KiB |
BIN
doc/assets/cros.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 984 KiB After Width: | Height: | Size: 603 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 18 KiB |
BIN
doc/assets/release.gif
Normal file
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 58 KiB |
@@ -1,6 +1,6 @@
|
|||||||
# Generate a self-signed certificate 🔒
|
# Generate a self-signed certificate 🔒
|
||||||
|
|
||||||
code-server has the ability to secure your connection between client and server using SSL/TSL certificates. By default, the server will start with an unencrypted connection. We recommend Self-signed TLS/SSL certificates for personal of code-server or within an organization.
|
code-server has the ability to secure your connection between client and server using SSL/TSL certificates. By default, the server will start with an unencrypted connection. We recommend Self-signed TLS/SSL certificates for personal use of code-server or within an organization.
|
||||||
|
|
||||||
This guide will show you how to create a self-signed certificate and start code-server using your certificate/key.
|
This guide will show you how to create a self-signed certificate and start code-server using your certificate/key.
|
||||||
|
|
||||||
|
|||||||
53
doc/self-hosted/cros-install.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Installng code-server in your ChromiumOS/ChromeOS/CloudReady machine
|
||||||
|
|
||||||
|
This guide will show you how to install code-server into your CrOS machine.
|
||||||
|
|
||||||
|
## Using Crostini
|
||||||
|
|
||||||
|
One of the easier ways to run code-server is via [Crostini](https://www.aboutchromebooks.com/tag/project-crostini/), the Linux apps support feature in CrOS. Make sure you have enough RAM, HDD space and your CPU has VT-x/ AMD-V support. If your chromebook has this, then you are qualified to use Crostini.
|
||||||
|
|
||||||
|
If you are running R69, you might want to enable this on [Chrome Flags](chrome://flags/#enable-experimental-crostini-ui). If you run R72, however, this is already enabled for you.
|
||||||
|
|
||||||
|
After checking your prerequisites, follow the steps in [the self-host install guide](index.md) on installing code-server. Once done, make sure code-server works by running it. After running it, simply go to `penguin.linux.test:8443` to access code-server. Now you should be greeted with this screen. If you did, congratulations, you have installed code-server in your Chromebook!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Alternatively, if you ran code-server in another container and you need the IP for that specific container, simply go to Termina's shell via `crosh` and type `vsh termina`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Loading extra module: /usr/share/crosh/dev.d/50-crosh.sh
|
||||||
|
Welcome to crosh, the Chrome OS developer shell.
|
||||||
|
|
||||||
|
If you got here by mistake, don't panic! Just close this tab and carry on.
|
||||||
|
|
||||||
|
Type 'help' for a list of commands.
|
||||||
|
|
||||||
|
If you want to customize the look/behavior, you can use the options page.
|
||||||
|
Load it by using the Ctrl+Shift+P keyboard shortcut.
|
||||||
|
|
||||||
|
crosh> vsh termina
|
||||||
|
(termina) chronos@localhost ~ $
|
||||||
|
```
|
||||||
|
While in termina, run `lxc list`. It should output the list of running containers.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(termina) chronos@localhost ~ $ lxc list
|
||||||
|
+---------+---------+-----------------------+------+------------+-----------+
|
||||||
|
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
|
||||||
|
+---------+---------+-----------------------+------+------------+-----------+
|
||||||
|
| penguin | RUNNING | 100.115.92.199 (eth0) | | PERSISTENT | 0 |
|
||||||
|
+---------+---------+-----------------------+------+------------+-----------+
|
||||||
|
(termina) chronos@localhost ~ $
|
||||||
|
```
|
||||||
|
|
||||||
|
For this example, we show the default `penguin` container, which is exposed on `eth0` at 100.115.92.199. Simply enter the IP of the container where the code-server runs to Chrome.
|
||||||
|
|
||||||
|
## Using Crouton
|
||||||
|
|
||||||
|
[Crouton](https://github.com/dnschneid/crouton) is one of the old ways to get a running full Linux via `chroot` on a Chromebook. To use crouton, enable developer mode and go to `crosh`. This time, run `shell`, which should drop you to `bash`.
|
||||||
|
|
||||||
|
Make sure you downloaded `crouton`, if so, go ahead and run it under `~/Downloads`. After installing your chroot container via crouton, go ahead and enter `enter-chroot` to enter your container.
|
||||||
|
|
||||||
|
Follow the instructions set in [the self-host install guide](index.md) to install code-server. After that is done, run `code-server` and verify it works by going to `localhost:8443`.
|
||||||
|
|
||||||
|
> At this point in writing, `localhost` seems to work in this method. However, the author is not sure if it applies still to newer Chromebooks.
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
[code-server](https://coder.com) is used by developers at Azure, Google, Reddit, and more to give them access to VS Code in the browser.
|
[code-server](https://coder.com) is used by developers at Azure, Google, Reddit, and more to give them access to VS Code in the browser.
|
||||||
|
|
||||||
## Quickstart guide
|
## Quickstart Guide
|
||||||
|
|
||||||
> NOTE: If you get stuck or need help, [file an issue](https://github.com/codercom/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide).
|
> NOTE: If you get stuck or need help, [file an issue](https://github.com/cdr/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide).
|
||||||
|
|
||||||
This document pertains to Coder specific implementations of VS Code. For documentation on how to use VS Code itself, please refer to the official [documentation for VS Code](https://code.visualstudio.com/docs)
|
This document pertains to Coder specific implementations of VS Code. For documentation on how to use VS Code itself, please refer to the official [documentation for VS Code](https://code.visualstudio.com/docs)
|
||||||
|
|
||||||
It takes just a few minutes to get your own self-hosted server running. If you've got a machine running macOS, Windows, or Linux, you're ready to start the binary which listens on port `8080` by default.
|
It takes just a few minutes to get your own self-hosted server running. If you've got a machine running macOS, Windows, or Linux, you're ready to start the binary which listens on port `8443` by default.
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
DO NOT CHANGE THIS TO A CODEBLOCK.
|
DO NOT CHANGE THIS TO A CODEBLOCK.
|
||||||
@@ -17,14 +17,14 @@ It takes just a few minutes to get your own self-hosted server running. If you'v
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
1. Visit [the releases](https://github.com/codercom/code-server/releases) page and download the latest cli for your operating system
|
1. Visit [the releases](https://github.com/cdr/code-server/releases) page and download the latest cli for your operating system
|
||||||
2. Double click the executable to run in the current directory
|
2. Double click the executable to run in the current directory
|
||||||
3. Copy the password that appears in the cli<img src="../assets/cli.png">
|
3. Copy the password that appears in the cli<img src="../assets/cli.png">
|
||||||
4. In your browser navigate to `localhost:8080`
|
4. In your browser navigate to `localhost:8443`
|
||||||
5. Paste the password from the cli into the login window<img src="../assets/server-password-modal.png">
|
5. Paste the password from the cli into the login window<img src="../assets/server-password-modal.png">
|
||||||
> NOTE: Be careful with your password as sharing it will grant those users access to your server's file system
|
> NOTE: Be careful with your password as sharing it will grant those users access to your server's file system
|
||||||
|
|
||||||
### Things to know
|
### Things To Know
|
||||||
- When you visit the IP for your code-server, you will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"**<img src ="../assets/chrome_warning.png">
|
- When you visit the IP for your code-server, you will be greeted with this page. Code-server is using a self-signed SSL certificate for easy setup. To proceed to the IDE, click **"Advanced"**<img src ="../assets/chrome_warning.png">
|
||||||
- Then click **"proceed anyway"**<img src="../assets/chrome_confirm.png">
|
- Then click **"proceed anyway"**<img src="../assets/chrome_confirm.png">
|
||||||
|
|
||||||
@@ -34,42 +34,90 @@ It takes just a few minutes to get your own self-hosted server running. If you'v
|
|||||||
code-server can be ran with a number of arguments to customize your working directory, host, port, and SSL certificate.
|
code-server can be ran with a number of arguments to customize your working directory, host, port, and SSL certificate.
|
||||||
|
|
||||||
```
|
```
|
||||||
USAGE
|
Usage: code-server [options]
|
||||||
$ code-server [WORKDIR]
|
|
||||||
|
|
||||||
ARGUMENTS
|
Run VS Code on a remote server.
|
||||||
WORKDIR [default: (directory to binary)] Specify working dir
|
|
||||||
|
|
||||||
OPTIONS
|
Options:
|
||||||
-d, --data-dir=data-dir
|
-V, --version output the version number
|
||||||
-h, --host=host [default: 0.0.0.0]
|
--cert <value>
|
||||||
-o, --open Open in browser on startup
|
--cert-key <value>
|
||||||
-p, --port=port [default: 8080] Port to bind on
|
-e, --extensions-dir <dir> Set the root path for extensions.
|
||||||
-v, --version show CLI version
|
-d --user-data-dir <dir> Specifies the directory that user data is kept in, useful when running as root.
|
||||||
--cert=cert
|
--data-dir <value> DEPRECATED: Use '--user-data-dir' instead. Customize where user-data is stored.
|
||||||
--cert-key=cert-key
|
-h, --host <value> Customize the hostname. (default: "0.0.0.0")
|
||||||
--help show CLI help
|
-o, --open Open in the browser on startup.
|
||||||
|
-p, --port <number> Port to bind on. (default: 8443)
|
||||||
|
-N, --no-auth Start without requiring authentication.
|
||||||
|
-H, --allow-http Allow http connections.
|
||||||
|
-P, --password <value> Specify a password for authentication.
|
||||||
|
--disable-telemetry Disables ALL telemetry.
|
||||||
|
--help output usage information
|
||||||
```
|
```
|
||||||
|
|
||||||
### Data directory
|
### Data Directory
|
||||||
Use `code-server -d (path/to/directory)` or `code-server --data-dir=(path/to/directory)`, excluding the parentheses to specify the root folder that VS Code will start in
|
Use `code-server -d (path/to/directory)` or `code-server --user-data-dir=(path/to/directory)`, excluding the parentheses to specify the root folder that VS Code will start in.
|
||||||
|
|
||||||
### Host
|
### Host
|
||||||
By default, code-server will use `0.0.0.0` as it's address. This can be changed by using `code-server -h` or `code-server --host=` followed by the address you want to use.
|
By default, code-server will use `0.0.0.0` as its address. This can be changed by using `code-server -h` or `code-server --host=` followed by the address you want to use.
|
||||||
> Example: `code-server -h 127.0.0.1`
|
> Example: `code-server -h 127.0.0.1`
|
||||||
|
|
||||||
### Open
|
### Open
|
||||||
You can have the server automatically open the VS Code in your browser on startup by using the `code server -o` or `code-server --open` flags
|
You can have the server automatically open the VS Code in your browser on startup by using the `code-server -o` or `code-server --open` flags
|
||||||
|
|
||||||
### Port
|
### Port
|
||||||
By default, code-server will use `8080` as it's port. This can be changed by using `code-server -p` or `code-server --port=` followed by the port you want to use.
|
By default, code-server will use `8443` as its port. This can be changed by using `code-server -p` or `code-server --port=` followed by the port you want to use.
|
||||||
> Example: `code-server -p 9000`
|
> Example: `code-server -p 9000`
|
||||||
|
|
||||||
|
### Telemetry
|
||||||
|
Disable all telemetry with `code-server --disable-telemetry`.
|
||||||
|
|
||||||
### Cert and Cert Key
|
### Cert and Cert Key
|
||||||
To encrypt the traffic between the browser and server use `code-server --cert=` followed by the path to your `.cer` file. Additionally, you can use certificate keys with `code-server --cert-key` followed by the path to your `.key` file.
|
To encrypt the traffic between the browser and server use `code-server --cert=` followed by the path to your `.cer` file. Additionally, you can use certificate keys with `code-server --cert-key` followed by the path to your `.key` file.
|
||||||
> Example (certificate and key): `code-server --cert /etc/letsencrypt/live/example.com/fullchain.cer --cert-key /etc/letsencrypt/live/example.com/fullchain.key`
|
> Example (certificate and key): `code-server --cert /etc/letsencrypt/live/example.com/fullchain.cer --cert-key /etc/letsencrypt/live/example.com/fullchain.key`
|
||||||
|
> Example (if you are using Letsencrypt or similar): `code-server --cert /etc/letsencrypt/live/example.com/fullchain.pem --cert-key /etc/letsencrypt/live/example.com/privkey.key`
|
||||||
|
|
||||||
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../security/ssl.md)
|
> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../security/ssl.md)
|
||||||
|
|
||||||
|
### Nginx Reverse Proxy
|
||||||
|
Nginx is for reverse proxy. Below is a virtual host example that works with code-server. Please also pass --allow-http. You can also use certbot by EFF to get a ssl certificates for free.
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name code.example.com code.example.org;
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8443/;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection upgrade;
|
||||||
|
proxy_set_header Accept-Encoding gzip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Apache Reverse Proxy
|
||||||
|
Example of https virtualhost configuration for Apache as a reverse proxy. Please also pass --allow-http on code-server startup to allow the proxy to connect.
|
||||||
|
```
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName code.example.com
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||||
|
RewriteRule /(.*) ws://localhost:8443/$1 [P,L]
|
||||||
|
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
|
||||||
|
RewriteRule /(.*) http://localhost:8443/$1 [P,L]
|
||||||
|
|
||||||
|
ProxyRequests off
|
||||||
|
|
||||||
|
RequestHeader set X-Forwarded-Proto https
|
||||||
|
RequestHeader set X-Forwarded-Port 443
|
||||||
|
|
||||||
|
ProxyPass / http://localhost:8443/ nocanon
|
||||||
|
ProxyPassReverse / http://localhost:8443/
|
||||||
|
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
*Important:* For more details about Apache reverse proxy configuration checkout the [documentation](https://httpd.apache.org/docs/current/mod/mod_proxy.html) - especially the [Securing your Server](https://httpd.apache.org/docs/current/mod/mod_proxy.html#access) section
|
||||||
|
|
||||||
### Help
|
### Help
|
||||||
Use `code-server -h` or `code-server --help` to view the usage for the cli. This is also shown at the beginning of this section.
|
Use `code-server --help` to view the usage for the CLI. This is also shown at the beginning of this section.
|
||||||
|
|||||||
17
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@coder/code-server",
|
"name": "@coder/code-server",
|
||||||
"repository": "https://github.com/codercom/code-server",
|
"repository": "https://github.com/cdr/code-server",
|
||||||
"author": "Coder",
|
"author": "Coder",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Run VS Code remotely.",
|
"description": "Run VS Code remotely.",
|
||||||
@@ -15,7 +15,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fs-extra": "^5.0.4",
|
"@types/fs-extra": "^5.0.4",
|
||||||
"@types/node": "^10.12.18",
|
"@types/node": "^10.12.18",
|
||||||
|
"@types/tar": "^4.0.0",
|
||||||
"@types/trash": "^4.3.1",
|
"@types/trash": "^4.3.1",
|
||||||
|
"cache-loader": "^2.0.1",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"crypto-browserify": "^3.12.0",
|
"crypto-browserify": "^3.12.0",
|
||||||
"css-loader": "^2.1.0",
|
"css-loader": "^2.1.0",
|
||||||
@@ -34,24 +36,35 @@
|
|||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"string-replace-loader": "^2.1.1",
|
"string-replace-loader": "^2.1.1",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
|
"tar": "^4.4.8",
|
||||||
|
"terser-webpack-plugin": "^1.2.3",
|
||||||
"ts-loader": "^5.3.3",
|
"ts-loader": "^5.3.3",
|
||||||
"ts-node": "^7.0.1",
|
"ts-node": "^7.0.1",
|
||||||
"tsconfig-paths": "^3.8.0",
|
"tsconfig-paths": "^3.8.0",
|
||||||
|
"tslib": "^1.9.3",
|
||||||
"tslint": "^5.12.1",
|
"tslint": "^5.12.1",
|
||||||
"typescript": "^3.2.2",
|
"typescript": "^3.2.2",
|
||||||
"typescript-tslint-plugin": "^0.2.1",
|
"typescript-tslint-plugin": "^0.2.1",
|
||||||
"uglifyjs-webpack-plugin": "^2.1.1",
|
"uglifyjs-webpack-plugin": "^2.1.1",
|
||||||
|
"url-loader": "^1.1.2",
|
||||||
|
"util": "^0.11.1",
|
||||||
"webpack": "^4.28.4",
|
"webpack": "^4.28.4",
|
||||||
"webpack-bundle-analyzer": "^3.0.3",
|
"webpack-bundle-analyzer": "^3.0.3",
|
||||||
"webpack-cli": "^3.2.1",
|
"webpack-cli": "^3.2.1",
|
||||||
"webpack-dev-middleware": "^3.5.0",
|
"webpack-dev-middleware": "^3.5.0",
|
||||||
"webpack-dev-server": "^3.1.14",
|
"webpack-dev-server": "^3.1.14",
|
||||||
"webpack-hot-middleware": "^2.24.3",
|
"webpack-hot-middleware": "^2.24.3",
|
||||||
|
"webpack-pwa-manifest": "^4.0.0",
|
||||||
|
"workbox-webpack-plugin": "^4.1.0",
|
||||||
"write-file-webpack-plugin": "^4.5.0"
|
"write-file-webpack-plugin": "^4.5.0"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"bindings": "1.3.0"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-loader": "^0.6.0",
|
"node-loader": "^0.6.0",
|
||||||
"trash": "^4.3.0",
|
"node-pty": "0.8.1",
|
||||||
|
"spdlog": "0.8.1",
|
||||||
"webpack-merge": "^4.2.1"
|
"webpack-merge": "^4.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,38 +7,24 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="login">
|
<form id="login-form">
|
||||||
<div class="back">
|
<div class="login">
|
||||||
<- Back </div>
|
<div class="back">
|
||||||
<h4 class="title">code-server</h4>
|
<- Back </div> <h4 class="title">code-server</h4>
|
||||||
<h2 class="subtitle">
|
<h2 class="subtitle">
|
||||||
Enter server password
|
Enter server password
|
||||||
</h2>
|
</h2>
|
||||||
<div class="mdc-text-field">
|
<div class="mdc-text-field">
|
||||||
<input type="password" id="password" class="mdc-text-field__input" required>
|
<input type="password" id="password" class="mdc-text-field__input" required>
|
||||||
<label class="mdc-floating-label" for="password">Password</label>
|
<label class="mdc-floating-label" for="password">Password</label>
|
||||||
<div class="mdc-line-ripple"></div>
|
<div class="mdc-line-ripple"></div>
|
||||||
</div>
|
|
||||||
<div class="mdc-text-field-helper-line">
|
|
||||||
<div class="mdc-text-field-helper-text">helper text</div>
|
|
||||||
</div>
|
|
||||||
<div class="mdc-form-field">
|
|
||||||
<div class="mdc-checkbox">
|
|
||||||
<input type="checkbox" class="mdc-checkbox__native-control" id="remember" />
|
|
||||||
<div class="mdc-checkbox__background">
|
|
||||||
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
|
|
||||||
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59" />
|
|
||||||
</svg>
|
|
||||||
<div class="mdc-checkbox__mixedmark"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<button id="submit" class="mdc-button mdc-button--unelevated">
|
||||||
<label for="remember">Remember Me</label>
|
<span class="mdc-button__label">Enter IDE</span>
|
||||||
</div>
|
</button>
|
||||||
<button id="submit" class="mdc-button mdc-button--unelevated">
|
<div id="error-display"></div>
|
||||||
<span class="mdc-button__label">Enter IDE</span>
|
</div>
|
||||||
</button>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -106,3 +106,16 @@ body {
|
|||||||
|
|
||||||
// transition: 500ms opacity ease;
|
// transition: 500ms opacity ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#error-display {
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #bb2d0f;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
line-height: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,11 +20,27 @@ window.addEventListener("message", (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const password = document.getElementById("password") as HTMLInputElement;
|
const password = document.getElementById("password") as HTMLInputElement;
|
||||||
const submit = document.getElementById("submit") as HTMLButtonElement;
|
const form = document.getElementById("login-form") as HTMLFormElement;
|
||||||
if (!submit) {
|
|
||||||
throw new Error("No submit button found");
|
if (!form) {
|
||||||
|
throw new Error("No password form found");
|
||||||
}
|
}
|
||||||
submit.addEventListener("click", () => {
|
|
||||||
document.cookie = `password=${password.value}`;
|
form.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
document.cookie = `password=${password.value}; `
|
||||||
|
+ `path=${location.pathname.replace(/\/login\/?$/, "/")}; `
|
||||||
|
+ `domain=${location.hostname}`;
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify user on load of page if previous password was unsuccessful
|
||||||
|
*/
|
||||||
|
const reg = new RegExp(`password=(\\w+);?`);
|
||||||
|
const matches = document.cookie.match(reg);
|
||||||
|
const errorDisplay = document.getElementById("error-display") as HTMLDivElement;
|
||||||
|
|
||||||
|
if (document.referrer === document.location.href && matches) {
|
||||||
|
errorDisplay.innerText = "Password is incorrect!";
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ const root = path.resolve(__dirname, "../../..");
|
|||||||
|
|
||||||
module.exports = merge(
|
module.exports = merge(
|
||||||
require(path.join(root, "scripts/webpack.client.config.js"))({
|
require(path.join(root, "scripts/webpack.client.config.js"))({
|
||||||
entry: path.join(root, "packages/app/browser/src/app.ts"),
|
dirname: __dirname,
|
||||||
template: path.join(root, "packages/app/browser/src/app.html"),
|
entry: path.join(__dirname, "src/app.ts"),
|
||||||
|
name: "login",
|
||||||
|
template: path.join(__dirname, "src/app.html"),
|
||||||
}), {
|
}), {
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, "out"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ export class Server extends React.Component<ServerProps, {
|
|||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: "Online",
|
status: "Online",
|
||||||
version: "v1.31.0",
|
version: process.env.VERSION,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -5,15 +5,12 @@ const root = path.resolve(__dirname, "../..");
|
|||||||
|
|
||||||
module.exports = merge(
|
module.exports = merge(
|
||||||
require(path.join(root, "scripts/webpack.node.config.js"))({
|
require(path.join(root, "scripts/webpack.node.config.js"))({
|
||||||
// Options.
|
dirname: __dirname,
|
||||||
|
name: "dns",
|
||||||
}), {
|
}), {
|
||||||
externals: {
|
externals: {
|
||||||
"node-named": "commonjs node-named",
|
"node-named": "commonjs node-named",
|
||||||
},
|
},
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, "out"),
|
|
||||||
filename: "main.js",
|
|
||||||
},
|
|
||||||
entry: [
|
entry: [
|
||||||
"./packages/dns/src/dns.ts"
|
"./packages/dns/src/dns.ts"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,28 +1,51 @@
|
|||||||
import { IDisposable } from "@coder/disposable";
|
import { IDisposable } from "@coder/disposable";
|
||||||
|
|
||||||
export interface Event<T> {
|
export interface Event<T> {
|
||||||
(listener: (e: T) => void): IDisposable;
|
(listener: (value: T) => void): IDisposable;
|
||||||
|
(id: number | string, listener: (value: T) => void): IDisposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitter typecasts for a single event type.
|
* Emitter typecasts for a single event type. You can optionally use IDs, but
|
||||||
|
* using undefined with IDs will not work. If you emit without an ID, *all*
|
||||||
|
* listeners regardless of their ID (or lack thereof) will receive the event.
|
||||||
|
* Similarly, if you listen without an ID you will get *all* events for any or
|
||||||
|
* no ID.
|
||||||
*/
|
*/
|
||||||
export class Emitter<T> {
|
export class Emitter<T> {
|
||||||
private listeners = <Array<(e: T) => void>>[];
|
private listeners = <Array<(value: T) => void>>[];
|
||||||
|
private readonly idListeners = new Map<number | string, Array<(value: T) => void>>();
|
||||||
|
|
||||||
public get event(): Event<T> {
|
public get event(): Event<T> {
|
||||||
return (cb: (e: T) => void): IDisposable => {
|
return (id: number | string | ((value: T) => void), cb?: (value: T) => void): IDisposable => {
|
||||||
if (this.listeners) {
|
if (typeof id !== "function") {
|
||||||
this.listeners.push(cb);
|
if (this.idListeners.has(id)) {
|
||||||
|
this.idListeners.get(id)!.push(cb!);
|
||||||
|
} else {
|
||||||
|
this.idListeners.set(id, [cb!]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dispose: (): void => {
|
||||||
|
if (this.idListeners.has(id)) {
|
||||||
|
const cbs = this.idListeners.get(id)!;
|
||||||
|
const i = cbs.indexOf(cb!);
|
||||||
|
if (i !== -1) {
|
||||||
|
cbs.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cb = id;
|
||||||
|
this.listeners.push(cb);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dispose: (): void => {
|
dispose: (): void => {
|
||||||
if (this.listeners) {
|
const i = this.listeners.indexOf(cb!);
|
||||||
const i = this.listeners.indexOf(cb);
|
if (i !== -1) {
|
||||||
if (i !== -1) {
|
this.listeners.splice(i, 1);
|
||||||
this.listeners.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -32,16 +55,45 @@ export class Emitter<T> {
|
|||||||
/**
|
/**
|
||||||
* Emit an event with a value.
|
* Emit an event with a value.
|
||||||
*/
|
*/
|
||||||
public emit(value: T): void {
|
public emit(value: T): void;
|
||||||
if (this.listeners) {
|
public emit(id: number | string, value: T): void;
|
||||||
this.listeners.forEach((t) => t(value));
|
public emit(id: number | string | T, value?: T): void {
|
||||||
|
if ((typeof id === "number" || typeof id === "string") && typeof value !== "undefined") {
|
||||||
|
if (this.idListeners.has(id)) {
|
||||||
|
this.idListeners.get(id)!.forEach((cb) => cb(value!));
|
||||||
|
}
|
||||||
|
this.listeners.forEach((cb) => cb(value!));
|
||||||
|
} else {
|
||||||
|
this.idListeners.forEach((cbs) => cbs.forEach((cb) => cb((id as T)!)));
|
||||||
|
this.listeners.forEach((cb) => cb((id as T)!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispose the current events.
|
* Dispose the current events.
|
||||||
*/
|
*/
|
||||||
public dispose(): void {
|
public dispose(): void;
|
||||||
this.listeners = [];
|
public dispose(id: number | string): void;
|
||||||
|
public dispose(id?: number | string): void {
|
||||||
|
if (typeof id !== "undefined") {
|
||||||
|
this.idListeners.delete(id);
|
||||||
|
} else {
|
||||||
|
this.listeners = [];
|
||||||
|
this.idListeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get counts(): { [key: string]: number } {
|
||||||
|
const counts = <{ [key: string]: number }>{};
|
||||||
|
if (this.listeners.length > 0) {
|
||||||
|
counts["n/a"] = this.listeners.length;
|
||||||
|
}
|
||||||
|
this.idListeners.forEach((cbs, id) => {
|
||||||
|
if (cbs.length > 0) {
|
||||||
|
counts[`${id}`] = cbs.length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return counts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
122
packages/events/test/events.test.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { Emitter } from "../src/events";
|
||||||
|
|
||||||
|
describe("Event", () => {
|
||||||
|
const emitter = new Emitter<number>();
|
||||||
|
|
||||||
|
it("should listen to global event", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const d = emitter.event(fn);
|
||||||
|
emitter.emit(10);
|
||||||
|
expect(fn).toHaveBeenCalledWith(10);
|
||||||
|
d.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should listen to id event", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const d = emitter.event(0, fn);
|
||||||
|
emitter.emit(0, 5);
|
||||||
|
expect(fn).toHaveBeenCalledWith(5);
|
||||||
|
d.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should listen to string id event", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const d = emitter.event("string", fn);
|
||||||
|
emitter.emit("string", 55);
|
||||||
|
expect(fn).toHaveBeenCalledWith(55);
|
||||||
|
d.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not listen wrong id event", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const d = emitter.event(1, fn);
|
||||||
|
emitter.emit(0, 5);
|
||||||
|
emitter.emit(1, 6);
|
||||||
|
expect(fn).toHaveBeenCalledWith(6);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
d.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should listen to id event globally", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const d = emitter.event(fn);
|
||||||
|
emitter.emit(1, 11);
|
||||||
|
expect(fn).toHaveBeenCalledWith(11);
|
||||||
|
d.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should listen to global event", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const d = emitter.event(3, fn);
|
||||||
|
emitter.emit(14);
|
||||||
|
expect(fn).toHaveBeenCalledWith(14);
|
||||||
|
d.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should listen to id event multiple times", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const disposers = [
|
||||||
|
emitter.event(934, fn),
|
||||||
|
emitter.event(934, fn),
|
||||||
|
emitter.event(934, fn),
|
||||||
|
emitter.event(934, fn),
|
||||||
|
];
|
||||||
|
emitter.emit(934, 324);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(4);
|
||||||
|
expect(fn).toHaveBeenCalledWith(324);
|
||||||
|
disposers.forEach((d) => d.dispose());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispose individually", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const d = emitter.event(fn);
|
||||||
|
|
||||||
|
const fn2 = jest.fn();
|
||||||
|
const d2 = emitter.event(1, fn2);
|
||||||
|
|
||||||
|
d.dispose();
|
||||||
|
|
||||||
|
emitter.emit(12);
|
||||||
|
emitter.emit(1, 12);
|
||||||
|
|
||||||
|
expect(fn).not.toBeCalled();
|
||||||
|
expect(fn2).toBeCalledTimes(2);
|
||||||
|
|
||||||
|
d2.dispose();
|
||||||
|
|
||||||
|
emitter.emit(12);
|
||||||
|
emitter.emit(1, 12);
|
||||||
|
|
||||||
|
expect(fn).not.toBeCalled();
|
||||||
|
expect(fn2).toBeCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispose by id", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
emitter.event(fn);
|
||||||
|
|
||||||
|
const fn2 = jest.fn();
|
||||||
|
emitter.event(1, fn2);
|
||||||
|
|
||||||
|
emitter.dispose(1);
|
||||||
|
|
||||||
|
emitter.emit(12);
|
||||||
|
emitter.emit(1, 12);
|
||||||
|
|
||||||
|
expect(fn).toBeCalledTimes(2);
|
||||||
|
expect(fn2).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispose all", () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
emitter.event(fn);
|
||||||
|
emitter.event(1, fn);
|
||||||
|
|
||||||
|
emitter.dispose();
|
||||||
|
|
||||||
|
emitter.emit(12);
|
||||||
|
emitter.emit(1, 12);
|
||||||
|
|
||||||
|
expect(fn).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
55
packages/ide-api/api.d.ts
vendored
@@ -1,3 +1,10 @@
|
|||||||
|
// tslint:disable no-any
|
||||||
|
|
||||||
|
import { ITerminalService } from "vs/workbench/contrib/terminal/common/terminal";
|
||||||
|
import { IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||||
|
import { Action } from 'vs/base/common/actions';
|
||||||
|
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||||
|
|
||||||
export interface EvalHelper { }
|
export interface EvalHelper { }
|
||||||
interface ActiveEvalEmitter {
|
interface ActiveEvalEmitter {
|
||||||
removeAllListeners(event?: string): void;
|
removeAllListeners(event?: string): void;
|
||||||
@@ -106,7 +113,7 @@ interface IMenuItem {
|
|||||||
command: ICommandAction;
|
command: ICommandAction;
|
||||||
alt?: ICommandAction;
|
alt?: ICommandAction;
|
||||||
// when?: ContextKeyExpr;
|
// when?: ContextKeyExpr;
|
||||||
group?: 'navigation' | string;
|
group?: "navigation" | string;
|
||||||
order?: number;
|
order?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,38 +141,44 @@ interface ICommandRegistry {
|
|||||||
registerCommand(command: ICommand): IDisposable;
|
registerCommand(command: ICommand): IDisposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare namespace ide {
|
interface IStorageService {
|
||||||
export const client: {
|
save(): Promise<void>;
|
||||||
run(func: (helper: ActiveEvalEmitter) => Disposer): ActiveEvalEmitter;
|
}
|
||||||
run<T1>(func: (helper: ActiveEvalEmitter, a1: T1) => Disposer, a1: T1): ActiveEvalEmitter;
|
|
||||||
run<T1, T2>(func: (helper: ActiveEvalEmitter, a1: T1, a2: T2) => Disposer, a1: T1, a2: T2): ActiveEvalEmitter;
|
|
||||||
run<T1, T2, T3>(func: (helper: ActiveEvalEmitter, a1: T1, a2: T2, a3: T3) => Disposer, a1: T1, a2: T2, a3: T3): ActiveEvalEmitter;
|
|
||||||
run<T1, T2, T3, T4>(func: (helper: ActiveEvalEmitter, a1: T1, a2: T2, a3: T3, a4: T4) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4): ActiveEvalEmitter;
|
|
||||||
run<T1, T2, T3, T4, T5>(func: (helper: ActiveEvalEmitter, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): ActiveEvalEmitter;
|
|
||||||
run<T1, T2, T3, T4, T5, T6>(func: (helper: ActiveEvalEmitter, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): ActiveEvalEmitter;
|
|
||||||
|
|
||||||
evaluate<R>(func: (helper: EvalHelper) => R | Promise<R>): Promise<R>;
|
declare namespace ide {
|
||||||
evaluate<R, T1>(func: (helper: EvalHelper, a1: T1) => R | Promise<R>, a1: T1): Promise<R>;
|
export const client: {};
|
||||||
evaluate<R, T1, T2>(func: (helper: EvalHelper, a1: T1, a2: T2) => R | Promise<R>, a1: T1, a2: T2): Promise<R>;
|
|
||||||
evaluate<R, T1, T2, T3>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3) => R | Promise<R>, a1: T1, a2: T2, a3: T3): Promise<R>;
|
|
||||||
evaluate<R, T1, T2, T3, T4>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4): Promise<R>;
|
|
||||||
evaluate<R, T1, T2, T3, T4, T5>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): Promise<R>;
|
|
||||||
evaluate<R, T1, T2, T3, T4, T5, T6>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): Promise<R>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const workbench: {
|
export const workbench: {
|
||||||
|
readonly action: Action,
|
||||||
|
readonly syncActionDescriptor: SyncActionDescriptor,
|
||||||
readonly statusbarService: IStatusbarService;
|
readonly statusbarService: IStatusbarService;
|
||||||
|
readonly actionsRegistry: IWorkbenchActionRegistry;
|
||||||
readonly notificationService: INotificationService;
|
readonly notificationService: INotificationService;
|
||||||
|
readonly storageService: IStorageService;
|
||||||
readonly menuRegistry: IMenuRegistry;
|
readonly menuRegistry: IMenuRegistry;
|
||||||
readonly commandRegistry: ICommandRegistry;
|
readonly commandRegistry: ICommandRegistry;
|
||||||
|
readonly terminalService: ITerminalService;
|
||||||
|
|
||||||
|
onFileCreate(cb: (path: string) => void): void;
|
||||||
|
onFileMove(cb: (path: string, target: string) => void): void;
|
||||||
|
onFileDelete(cb: (path: string) => void): void;
|
||||||
|
onFileSaved(cb: (path: string) => void): void;
|
||||||
|
onFileCopy(cb: (path: string, target: string) => void): void;
|
||||||
|
|
||||||
|
onModelAdded(cb: (path: string, languageId: string) => void): void;
|
||||||
|
onModelRemoved(cb: (path: string, languageId: string) => void): void;
|
||||||
|
onModelLanguageChange(cb: (path: string, languageId: string, oldLanguageId: string) => void): void;
|
||||||
|
|
||||||
|
onTerminalAdded(cb: () => void): void;
|
||||||
|
onTerminalRemoved(cb: () => void): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum Severity {
|
export enum Severity {
|
||||||
Ignore = 0,
|
Ignore = 0,
|
||||||
Info = 1,
|
Info = 1,
|
||||||
Warning = 2,
|
Warning = 2,
|
||||||
Error = 3
|
Error = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum StatusbarAlignment {
|
export enum StatusbarAlignment {
|
||||||
LEFT = 0,
|
LEFT = 0,
|
||||||
@@ -216,7 +229,7 @@ declare namespace ide {
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
ide?: typeof ide;
|
ide?: typeof ide;
|
||||||
|
|
||||||
addEventListener(event: "ide-ready", callback: (ide: CustomEvent & { readonly ide: typeof ide }) => void): void;
|
addEventListener(event: "ide-ready", callback: (ide: CustomEvent & { readonly ide: typeof ide }) => void): void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@coder/ide-api",
|
"name": "@coder/ide-api",
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"typings": "api.d.ts",
|
"typings": "api.d.ts",
|
||||||
"author": "Coder",
|
"author": "Coder",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@coder/ide",
|
"name": "@coder/ide",
|
||||||
"description": "Browser-based IDE client abstraction.",
|
"description": "Browser-based IDE client abstraction.",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts"
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/rimraf": "^2.0.2",
|
|
||||||
"rimraf": "^2.6.3"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ export abstract class IdeClient {
|
|||||||
this.loadTime = time(2500);
|
this.loadTime = time(2500);
|
||||||
|
|
||||||
let appWindow: Window | undefined;
|
let appWindow: Window | undefined;
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", (e) => {
|
||||||
|
e.preventDefault(); // FireFox
|
||||||
|
e.returnValue = ""; // Chrome
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener("message", (event) => {
|
||||||
if (event.data === "app") {
|
if (event.data === "app") {
|
||||||
appWindow = event.source as Window;
|
appWindow = event.source as Window;
|
||||||
@@ -41,7 +47,16 @@ export abstract class IdeClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.sharedProcessData = new Promise((resolve): void => {
|
this.sharedProcessData = new Promise((resolve): void => {
|
||||||
client.onSharedProcessActive(resolve);
|
let d = client.onSharedProcessActive((data) => {
|
||||||
|
d.dispose();
|
||||||
|
d = client.onSharedProcessActive(() => {
|
||||||
|
d.dispose();
|
||||||
|
this.retry.notificationService.error(
|
||||||
|
new Error("Disconnected from shared process. Searching, installing, enabling, and disabling extensions will not work until the page is refreshed."),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
resolve(data);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("contextmenu", (event) => {
|
window.addEventListener("contextmenu", (event) => {
|
||||||
@@ -65,10 +80,6 @@ export abstract class IdeClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap a task in some logging, timing, and progress updates. Can optionally
|
|
||||||
* wait on other tasks which won't count towards this task's time.
|
|
||||||
*/
|
|
||||||
public async task<T>(description: string, duration: number, task: () => Promise<T>): Promise<T>;
|
public async task<T>(description: string, duration: number, task: () => Promise<T>): Promise<T>;
|
||||||
public async task<T, V>(description: string, duration: number, task: (v: V) => Promise<T>, t: Promise<V>): Promise<T>;
|
public async task<T, V>(description: string, duration: number, task: (v: V) => Promise<T>, t: Promise<V>): Promise<T>;
|
||||||
public async task<T, V1, V2>(description: string, duration: number, task: (v1: V1, v2: V2) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>): Promise<T>;
|
public async task<T, V1, V2>(description: string, duration: number, task: (v1: V1, v2: V2) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>): Promise<T>;
|
||||||
@@ -76,6 +87,10 @@ export abstract class IdeClient {
|
|||||||
public async task<T, V1, V2, V3, V4>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>): Promise<T>;
|
public async task<T, V1, V2, V3, V4>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>): Promise<T>;
|
||||||
public async task<T, V1, V2, V3, V4, V5>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>): Promise<T>;
|
public async task<T, V1, V2, V3, V4, V5>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>): Promise<T>;
|
||||||
public async task<T, V1, V2, V3, V4, V5, V6>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>, t6: Promise<V6>): Promise<T>;
|
public async task<T, V1, V2, V3, V4, V5, V6>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>, t6: Promise<V6>): Promise<T>;
|
||||||
|
/**
|
||||||
|
* Wrap a task in some logging, timing, and progress updates. Can optionally
|
||||||
|
* wait on other tasks which won't count towards this task's time.
|
||||||
|
*/
|
||||||
public async task<T>(
|
public async task<T>(
|
||||||
description: string, duration: number = 100, task: (...args: any[]) => Promise<T>, ...after: Array<Promise<any>> // tslint:disable-line no-any
|
description: string, duration: number = 100, task: (...args: any[]) => Promise<T>, ...after: Array<Promise<any>> // tslint:disable-line no-any
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
|
|||||||
@@ -1,195 +1,4 @@
|
|||||||
import * as cp from "child_process";
|
import { Module } from "@coder/protocol";
|
||||||
import * as net from "net";
|
|
||||||
import * as stream from "stream";
|
|
||||||
import { CallbackEmitter, ActiveEvalReadable, ActiveEvalWritable } from "@coder/protocol";
|
|
||||||
import { client } from "./client";
|
import { client } from "./client";
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
declare var __non_webpack_require__: typeof require;
|
export = client.modules[Module.ChildProcess];
|
||||||
|
|
||||||
class ChildProcess extends CallbackEmitter implements cp.ChildProcess {
|
|
||||||
private _connected: boolean = false;
|
|
||||||
private _killed: boolean = false;
|
|
||||||
private _pid = -1;
|
|
||||||
public readonly stdin: stream.Writable;
|
|
||||||
public readonly stdout: stream.Readable;
|
|
||||||
public readonly stderr: stream.Readable;
|
|
||||||
// We need the explicit type otherwise TypeScript thinks it is (Writable | Readable)[].
|
|
||||||
public readonly stdio: [stream.Writable, stream.Readable, stream.Readable] = [this.stdin, this.stdout, this.stderr];
|
|
||||||
|
|
||||||
// tslint:disable no-any
|
|
||||||
public constructor(method: "exec", command: string, options?: { encoding?: string | null } & cp.ExecOptions | null, callback?: (...args: any[]) => void);
|
|
||||||
public constructor(method: "fork", modulePath: string, options?: cp.ForkOptions, args?: string[]);
|
|
||||||
public constructor(method: "spawn", command: string, options?: cp.SpawnOptions, args?: string[]);
|
|
||||||
public constructor(method: "exec" | "spawn" | "fork", command: string, options: object = {}, callback?: string[] | ((...args: any[]) => void)) {
|
|
||||||
// tslint:enable no-any
|
|
||||||
super();
|
|
||||||
|
|
||||||
let args: string[] = [];
|
|
||||||
if (Array.isArray(callback)) {
|
|
||||||
args = callback;
|
|
||||||
callback = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ae = client.run((ae, command, method, args, options, callbackId) => {
|
|
||||||
const cp = __non_webpack_require__("child_process") as typeof import("child_process");
|
|
||||||
|
|
||||||
ae.preserveEnv(options);
|
|
||||||
|
|
||||||
let childProcess: cp.ChildProcess;
|
|
||||||
switch (method) {
|
|
||||||
case "exec":
|
|
||||||
childProcess = cp.exec(command, options, ae.maybeCallback(callbackId));
|
|
||||||
break;
|
|
||||||
case "spawn":
|
|
||||||
childProcess = cp.spawn(command, args, options);
|
|
||||||
break;
|
|
||||||
case "fork":
|
|
||||||
childProcess = ae.fork(command, args, options);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`invalid method ${method}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
ae.on("disconnect", () => childProcess.disconnect());
|
|
||||||
ae.on("kill", (signal: string) => childProcess.kill(signal));
|
|
||||||
ae.on("ref", () => childProcess.ref());
|
|
||||||
ae.on("send", (message: string, callbackId: number) => childProcess.send(message, ae.maybeCallback(callbackId)));
|
|
||||||
ae.on("unref", () => childProcess.unref());
|
|
||||||
|
|
||||||
ae.emit("pid", childProcess.pid);
|
|
||||||
childProcess.on("close", (code, signal) => ae.emit("close", code, signal));
|
|
||||||
childProcess.on("disconnect", () => ae.emit("disconnect"));
|
|
||||||
childProcess.on("error", (error) => ae.emit("error", error));
|
|
||||||
childProcess.on("exit", (code, signal) => ae.emit("exit", code, signal));
|
|
||||||
childProcess.on("message", (message) => ae.emit("message", message));
|
|
||||||
|
|
||||||
if (childProcess.stdin) {
|
|
||||||
const stdinAe = ae.createUnique("stdin");
|
|
||||||
stdinAe.bindWritable(childProcess.stdin);
|
|
||||||
}
|
|
||||||
if (childProcess.stdout) {
|
|
||||||
const stdoutAe = ae.createUnique("stdout");
|
|
||||||
stdoutAe.bindReadable(childProcess.stdout);
|
|
||||||
}
|
|
||||||
if (childProcess.stderr) {
|
|
||||||
const stderrAe = ae.createUnique("stderr");
|
|
||||||
stderrAe.bindReadable(childProcess.stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
onDidDispose: (cb): cp.ChildProcess => childProcess.on("close", cb),
|
|
||||||
dispose: (): void => {
|
|
||||||
childProcess.kill();
|
|
||||||
setTimeout(() => childProcess.kill("SIGKILL"), 5000); // Double tap.
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, command, method, args, options, this.storeCallback(callback));
|
|
||||||
|
|
||||||
this.ae.on("pid", (pid) => {
|
|
||||||
this._pid = pid;
|
|
||||||
this._connected = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.stdin = new ActiveEvalWritable(this.ae.createUnique("stdin"));
|
|
||||||
this.stdout = new ActiveEvalReadable(this.ae.createUnique("stdout"));
|
|
||||||
this.stderr = new ActiveEvalReadable(this.ae.createUnique("stderr"));
|
|
||||||
|
|
||||||
this.ae.on("close", (code, signal) => this.emit("close", code, signal));
|
|
||||||
this.ae.on("disconnect", () => this.emit("disconnect"));
|
|
||||||
this.ae.on("error", (error) => this.emit("error", error));
|
|
||||||
this.ae.on("exit", (code, signal) => {
|
|
||||||
this._connected = false;
|
|
||||||
this._killed = true;
|
|
||||||
this.emit("exit", code, signal);
|
|
||||||
});
|
|
||||||
this.ae.on("message", (message) => this.emit("message", message));
|
|
||||||
}
|
|
||||||
|
|
||||||
public get pid(): number { return this._pid; }
|
|
||||||
public get connected(): boolean { return this._connected; }
|
|
||||||
public get killed(): boolean { return this._killed; }
|
|
||||||
|
|
||||||
public kill(): void { this.ae.emit("kill"); }
|
|
||||||
public disconnect(): void { this.ae.emit("disconnect"); }
|
|
||||||
public ref(): void { this.ae.emit("ref"); }
|
|
||||||
public unref(): void { this.ae.emit("unref"); }
|
|
||||||
|
|
||||||
public send(
|
|
||||||
message: any, // tslint:disable-line no-any to match spec
|
|
||||||
sendHandle?: net.Socket | net.Server | ((error: Error) => void),
|
|
||||||
options?: cp.MessageOptions | ((error: Error) => void),
|
|
||||||
callback?: (error: Error) => void): boolean {
|
|
||||||
if (typeof sendHandle === "function") {
|
|
||||||
callback = sendHandle;
|
|
||||||
sendHandle = undefined;
|
|
||||||
} else if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
if (sendHandle || options) {
|
|
||||||
throw new Error("sendHandle and options are not supported");
|
|
||||||
}
|
|
||||||
this.ae.emit("send", message, this.storeCallback(callback));
|
|
||||||
|
|
||||||
// Unfortunately this will always have to be true since we can't retrieve
|
|
||||||
// the actual response synchronously.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CP {
|
|
||||||
public readonly ChildProcess = ChildProcess;
|
|
||||||
|
|
||||||
public exec = (
|
|
||||||
command: string,
|
|
||||||
options?: { encoding?: string | null } & cp.ExecOptions | null | ((error: cp.ExecException | null, stdout: string, stderr: string) => void) | ((error: cp.ExecException | null, stdout: Buffer, stderr: Buffer) => void),
|
|
||||||
callback?: ((error: cp.ExecException | null, stdout: string, stderr: string) => void) | ((error: cp.ExecException | null, stdout: Buffer, stderr: Buffer) => void),
|
|
||||||
): cp.ChildProcess => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ChildProcess("exec", command, options, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public fork = (modulePath: string, args?: string[] | cp.ForkOptions, options?: cp.ForkOptions): cp.ChildProcess => {
|
|
||||||
if (args && !Array.isArray(args)) {
|
|
||||||
options = args;
|
|
||||||
args = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ChildProcess("fork", modulePath, options, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public spawn = (command: string, args?: string[] | cp.SpawnOptions, options?: cp.SpawnOptions): cp.ChildProcess => {
|
|
||||||
if (args && !Array.isArray(args)) {
|
|
||||||
options = args;
|
|
||||||
args = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ChildProcess("spawn", command, options, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fillCp = new CP();
|
|
||||||
// Methods that don't follow the standard callback pattern (an error followed
|
|
||||||
// by a single result) need to provide a custom promisify function.
|
|
||||||
Object.defineProperty(fillCp.exec, promisify.custom, {
|
|
||||||
value: (
|
|
||||||
command: string,
|
|
||||||
options?: { encoding?: string | null } & cp.ExecOptions | null,
|
|
||||||
): Promise<{ stdout: string | Buffer, stderr: string | Buffer }> => {
|
|
||||||
return new Promise((resolve, reject): void => {
|
|
||||||
fillCp.exec(command, options, (error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve({ stdout, stderr });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export = fillCp;
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class WebsocketConnection implements ReadWriteConnection {
|
|||||||
private activeSocket: WebSocket | undefined;
|
private activeSocket: WebSocket | undefined;
|
||||||
private readonly messageBuffer = <Uint8Array[]>[];
|
private readonly messageBuffer = <Uint8Array[]>[];
|
||||||
private readonly socketTimeoutDelay = 60 * 1000;
|
private readonly socketTimeoutDelay = 60 * 1000;
|
||||||
private readonly retryName = "Socket";
|
private readonly retry = retry.register("Socket", () => this.connect());
|
||||||
private isUp: boolean = false;
|
private isUp: boolean = false;
|
||||||
private closed: boolean = false;
|
private closed: boolean = false;
|
||||||
|
|
||||||
@@ -26,11 +26,14 @@ class WebsocketConnection implements ReadWriteConnection {
|
|||||||
public readonly onMessage = this.messageEmitter.event;
|
public readonly onMessage = this.messageEmitter.event;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
retry.register(this.retryName, () => this.connect());
|
this.retry.block();
|
||||||
retry.block(this.retryName);
|
this.retry.run();
|
||||||
retry.run(this.retryName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data across the socket. If closed, will error. If connecting, will
|
||||||
|
* queue.
|
||||||
|
*/
|
||||||
public send(data: Buffer | Uint8Array): void {
|
public send(data: Buffer | Uint8Array): void {
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
throw new Error("web socket is closed");
|
throw new Error("web socket is closed");
|
||||||
@@ -42,6 +45,9 @@ class WebsocketConnection implements ReadWriteConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close socket connection.
|
||||||
|
*/
|
||||||
public close(): void {
|
public close(): void {
|
||||||
this.closed = true;
|
this.closed = true;
|
||||||
this.dispose();
|
this.dispose();
|
||||||
@@ -61,7 +67,12 @@ class WebsocketConnection implements ReadWriteConnection {
|
|||||||
socket.addEventListener("close", (event) => {
|
socket.addEventListener("close", (event) => {
|
||||||
if (this.isUp) {
|
if (this.isUp) {
|
||||||
this.isUp = false;
|
this.isUp = false;
|
||||||
this.downEmitter.emit(undefined);
|
try {
|
||||||
|
this.downEmitter.emit(undefined);
|
||||||
|
} catch (error) {
|
||||||
|
// Don't let errors here prevent restarting.
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Web socket closed",
|
"Web socket closed",
|
||||||
@@ -70,8 +81,8 @@ class WebsocketConnection implements ReadWriteConnection {
|
|||||||
field("wasClean", event.wasClean),
|
field("wasClean", event.wasClean),
|
||||||
);
|
);
|
||||||
if (!this.closed) {
|
if (!this.closed) {
|
||||||
retry.block(this.retryName);
|
this.retry.block();
|
||||||
retry.run(this.retryName);
|
this.retry.run();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,8 +102,9 @@ class WebsocketConnection implements ReadWriteConnection {
|
|||||||
*/
|
*/
|
||||||
private async openSocket(): Promise<WebSocket> {
|
private async openSocket(): Promise<WebSocket> {
|
||||||
this.dispose();
|
this.dispose();
|
||||||
|
const wsProto = location.protocol === "https:" ? "wss" : "ws";
|
||||||
const socket = new WebSocket(
|
const socket = new WebSocket(
|
||||||
`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}`,
|
`${wsProto}://${location.host}${location.pathname}`,
|
||||||
);
|
);
|
||||||
socket.binaryType = "arraybuffer";
|
socket.binaryType = "arraybuffer";
|
||||||
this.activeSocket = socket;
|
this.activeSocket = socket;
|
||||||
@@ -102,15 +114,19 @@ class WebsocketConnection implements ReadWriteConnection {
|
|||||||
}, this.socketTimeoutDelay);
|
}, this.socketTimeoutDelay);
|
||||||
|
|
||||||
await new Promise((resolve, reject): void => {
|
await new Promise((resolve, reject): void => {
|
||||||
const onClose = (): void => {
|
const doReject = (): void => {
|
||||||
clearTimeout(socketWaitTimeout);
|
clearTimeout(socketWaitTimeout);
|
||||||
socket.removeEventListener("close", onClose);
|
socket.removeEventListener("error", doReject);
|
||||||
|
socket.removeEventListener("close", doReject);
|
||||||
reject();
|
reject();
|
||||||
};
|
};
|
||||||
socket.addEventListener("close", onClose);
|
socket.addEventListener("error", doReject);
|
||||||
|
socket.addEventListener("close", doReject);
|
||||||
|
|
||||||
socket.addEventListener("open", async () => {
|
socket.addEventListener("open", () => {
|
||||||
clearTimeout(socketWaitTimeout);
|
clearTimeout(socketWaitTimeout);
|
||||||
|
socket.removeEventListener("error", doReject);
|
||||||
|
socket.removeEventListener("close", doReject);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
background: #141414;
|
background: #141414;
|
||||||
border: none;
|
border: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-bottom: 25px;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -31,11 +30,11 @@
|
|||||||
|
|
||||||
.msgbox > .detail {
|
.msgbox > .detail {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-top: 5px;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msgbox > .errors {
|
.msgbox > .errors {
|
||||||
margin-bottom: 25px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msgbox > .errors {
|
.msgbox > .errors {
|
||||||
@@ -46,6 +45,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msgbox > .button-wrapper > button {
|
.msgbox > .button-wrapper > button {
|
||||||
|
|||||||
@@ -31,30 +31,31 @@ export class Dialog {
|
|||||||
private input: HTMLInputElement | undefined;
|
private input: HTMLInputElement | undefined;
|
||||||
private errors: HTMLElement;
|
private errors: HTMLElement;
|
||||||
private buttons: HTMLElement[] | undefined;
|
private buttons: HTMLElement[] | undefined;
|
||||||
|
private readonly msgBox: HTMLElement;
|
||||||
|
|
||||||
private actionEmitter = new Emitter<IDialogAction>();
|
private actionEmitter = new Emitter<IDialogAction>();
|
||||||
public onAction = this.actionEmitter.event;
|
public onAction = this.actionEmitter.event;
|
||||||
|
|
||||||
public constructor(private readonly options: IDialogOptions) {
|
public constructor(private readonly options: IDialogOptions) {
|
||||||
const msgBox = document.createElement("div");
|
this.msgBox = document.createElement("div");
|
||||||
msgBox.classList.add("msgbox");
|
this.msgBox.classList.add("msgbox");
|
||||||
|
|
||||||
if (this.options.message) {
|
if (this.options.message) {
|
||||||
const messageDiv = document.createElement("div");
|
const messageDiv = document.createElement("div");
|
||||||
messageDiv.classList.add("msg");
|
messageDiv.classList.add("msg");
|
||||||
messageDiv.innerText = this.options.message;
|
messageDiv.innerText = this.options.message;
|
||||||
msgBox.appendChild(messageDiv);
|
this.msgBox.appendChild(messageDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.detail) {
|
if (this.options.detail) {
|
||||||
const detailDiv = document.createElement("div");
|
const detailDiv = document.createElement("div");
|
||||||
detailDiv.classList.add("detail");
|
detailDiv.classList.add("detail");
|
||||||
detailDiv.innerText = this.options.detail;
|
detailDiv.innerText = this.options.detail;
|
||||||
msgBox.appendChild(detailDiv);
|
this.msgBox.appendChild(detailDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.input) {
|
if (this.options.input) {
|
||||||
msgBox.classList.add("input");
|
this.msgBox.classList.add("input");
|
||||||
this.input = document.createElement("input");
|
this.input = document.createElement("input");
|
||||||
this.input.classList.add("input");
|
this.input.classList.add("input");
|
||||||
this.input.value = this.options.input.value;
|
this.input.value = this.options.input.value;
|
||||||
@@ -67,12 +68,11 @@ export class Dialog {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
msgBox.appendChild(this.input);
|
this.msgBox.appendChild(this.input);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errors = document.createElement("div");
|
this.errors = document.createElement("div");
|
||||||
this.errors.classList.add("errors");
|
this.errors.classList.add("errors");
|
||||||
msgBox.appendChild(this.errors);
|
|
||||||
|
|
||||||
if (this.options.buttons && this.options.buttons.length > 0) {
|
if (this.options.buttons && this.options.buttons.length > 0) {
|
||||||
this.buttons = this.options.buttons.map((buttonText, buttonIndex) => {
|
this.buttons = this.options.buttons.map((buttonText, buttonIndex) => {
|
||||||
@@ -92,12 +92,12 @@ export class Dialog {
|
|||||||
const buttonWrapper = document.createElement("div");
|
const buttonWrapper = document.createElement("div");
|
||||||
buttonWrapper.classList.add("button-wrapper");
|
buttonWrapper.classList.add("button-wrapper");
|
||||||
this.buttons.forEach((b) => buttonWrapper.appendChild(b));
|
this.buttons.forEach((b) => buttonWrapper.appendChild(b));
|
||||||
msgBox.appendChild(buttonWrapper);
|
this.msgBox.appendChild(buttonWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.overlay = document.createElement("div");
|
this.overlay = document.createElement("div");
|
||||||
this.overlay.className = "msgbox-overlay";
|
this.overlay.className = "msgbox-overlay";
|
||||||
this.overlay.appendChild(msgBox);
|
this.overlay.appendChild(this.msgBox);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.overlay.style.opacity = "1";
|
this.overlay.style.opacity = "1";
|
||||||
@@ -122,6 +122,7 @@ export class Dialog {
|
|||||||
const errorDiv = document.createElement("error");
|
const errorDiv = document.createElement("error");
|
||||||
errorDiv.innerText = error;
|
errorDiv.innerText = error;
|
||||||
this.errors.appendChild(errorDiv);
|
this.errors.appendChild(errorDiv);
|
||||||
|
this.msgBox.appendChild(this.errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +132,7 @@ export class Dialog {
|
|||||||
public show(): void {
|
public show(): void {
|
||||||
if (!this.cachedActiveElement) {
|
if (!this.cachedActiveElement) {
|
||||||
this.cachedActiveElement = document.activeElement as HTMLElement;
|
this.cachedActiveElement = document.activeElement as HTMLElement;
|
||||||
document.body.appendChild(this.overlay);
|
(document.querySelector(".monaco-workbench") || document.body).appendChild(this.overlay);
|
||||||
document.addEventListener("keydown", this.onKeydown);
|
document.addEventListener("keydown", this.onKeydown);
|
||||||
if (this.input) {
|
if (this.input) {
|
||||||
this.input.focus();
|
this.input.focus();
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
/// <reference path="../../../../lib/vscode/src/typings/electron.d.ts" />
|
/// <reference path="../../../../lib/vscode/src/typings/electron.d.ts" />
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import * as trash from "trash";
|
||||||
import { logger, field } from "@coder/logger";
|
import { logger, field } from "@coder/logger";
|
||||||
import { IKey, Dialog as DialogBox } from "./dialog";
|
import { IKey, Dialog as DialogBox } from "./dialog";
|
||||||
import { clipboard } from "./clipboard";
|
import { clipboard } from "./clipboard";
|
||||||
import { client } from "./client";
|
|
||||||
|
|
||||||
declare var __non_webpack_require__: typeof require;
|
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
// tslint:disable-next-line no-any
|
||||||
(global as any).getOpenUrls = (): string[] => {
|
(global as any).getOpenUrls = (): string[] => {
|
||||||
@@ -25,18 +23,40 @@ const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HT
|
|||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
return oldCreateElement.call(document, tagName as any);
|
return oldCreateElement.call(document, tagName as any);
|
||||||
};
|
};
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
const getPropertyDescriptor = (object: any, id: string): PropertyDescriptor | undefined => {
|
||||||
|
let op = Object.getPrototypeOf(object);
|
||||||
|
while (!Object.getOwnPropertyDescriptor(op, id)) {
|
||||||
|
op = Object.getPrototypeOf(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.getOwnPropertyDescriptor(op, id);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tagName === "img") {
|
||||||
|
const img = createElement("img");
|
||||||
|
const oldSrc = getPropertyDescriptor(img, "src");
|
||||||
|
if (!oldSrc) {
|
||||||
|
throw new Error("Failed to find src property");
|
||||||
|
}
|
||||||
|
Object.defineProperty(img, "src", {
|
||||||
|
get: (): string => {
|
||||||
|
return oldSrc!.get!.call(img);
|
||||||
|
},
|
||||||
|
set: (value: string): void => {
|
||||||
|
if (value) {
|
||||||
|
const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource";
|
||||||
|
value = value.replace(/file:\/\//g, resourceBaseUrl);
|
||||||
|
}
|
||||||
|
oldSrc!.set!.call(img, value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
if (tagName === "style") {
|
if (tagName === "style") {
|
||||||
const style = createElement("style");
|
const style = createElement("style");
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
const getPropertyDescriptor = (object: any, id: string): PropertyDescriptor | undefined => {
|
|
||||||
let op = Object.getPrototypeOf(object);
|
|
||||||
while (!Object.getOwnPropertyDescriptor(op, id)) {
|
|
||||||
op = Object.getPrototypeOf(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.getOwnPropertyDescriptor(op, id);
|
|
||||||
};
|
|
||||||
const oldInnerHtml = getPropertyDescriptor(style, "innerHTML");
|
const oldInnerHtml = getPropertyDescriptor(style, "innerHTML");
|
||||||
if (!oldInnerHtml) {
|
if (!oldInnerHtml) {
|
||||||
throw new Error("Failed to find innerHTML property");
|
throw new Error("Failed to find innerHTML property");
|
||||||
@@ -46,7 +66,10 @@ const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HT
|
|||||||
return oldInnerHtml!.get!.call(style);
|
return oldInnerHtml!.get!.call(style);
|
||||||
},
|
},
|
||||||
set: (value: string): void => {
|
set: (value: string): void => {
|
||||||
value = value.replace(/file:\/\//g, "/resource");
|
if (value) {
|
||||||
|
const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource";
|
||||||
|
value = value.replace(/file:\/\//g, resourceBaseUrl);
|
||||||
|
}
|
||||||
oldInnerHtml!.set!.call(style, value);
|
oldInnerHtml!.set!.call(style, value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -59,7 +82,8 @@ const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HT
|
|||||||
if (sheet && !overridden) {
|
if (sheet && !overridden) {
|
||||||
const oldInsertRule = sheet.insertRule;
|
const oldInsertRule = sheet.insertRule;
|
||||||
sheet.insertRule = (rule: string, index?: number): void => {
|
sheet.insertRule = (rule: string, index?: number): void => {
|
||||||
rule = rule.replace(/file:\/\//g, "/resource");
|
const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource";
|
||||||
|
rule = rule.replace(/file:\/\//g, resourceBaseUrl);
|
||||||
oldInsertRule.call(sheet, rule, index);
|
oldInsertRule.call(sheet, rule, index);
|
||||||
};
|
};
|
||||||
overridden = true;
|
overridden = true;
|
||||||
@@ -116,12 +140,18 @@ const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HT
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
view.src = require("!!file-loader?name=[path][name].[ext]!./webview.html");
|
||||||
|
Object.defineProperty(view, "src", {
|
||||||
|
set: (): void => { /* Nope. */ },
|
||||||
|
});
|
||||||
(view as any).getWebContents = (): void => undefined; // tslint:disable-line no-any
|
(view as any).getWebContents = (): void => undefined; // tslint:disable-line no-any
|
||||||
(view as any).send = (channel: string, ...args: any[]): void => { // tslint:disable-line no-any
|
(view as any).send = (channel: string, ...args: any[]): void => { // tslint:disable-line no-any
|
||||||
if (args[0] && typeof args[0] === "object" && args[0].contents) {
|
if (args[0] && typeof args[0] === "object" && args[0].contents) {
|
||||||
// TODO
|
// TODO
|
||||||
args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m1) => `"/resource${m1}"`);
|
const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource";
|
||||||
args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m, m1) => `"/resource${m1}"`);
|
args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m1) => `"${resourceBaseUrl}${m1}"`);
|
||||||
|
args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m, m1) => `"${resourceBaseUrl}${m1}"`);
|
||||||
|
args[0].contents = (args[0].contents as string).replace(/style-src vscode-core-resource:/g, "style-src 'self'");
|
||||||
}
|
}
|
||||||
if (view.contentWindow) {
|
if (view.contentWindow) {
|
||||||
view.contentWindow.postMessage({
|
view.contentWindow.postMessage({
|
||||||
@@ -141,8 +171,10 @@ const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HT
|
|||||||
document.createElement = newCreateElement;
|
document.createElement = newCreateElement;
|
||||||
|
|
||||||
class Clipboard {
|
class Clipboard {
|
||||||
public has(): boolean {
|
private readonly buffers = new Map<string, Buffer>();
|
||||||
return false;
|
|
||||||
|
public has(format: string): boolean {
|
||||||
|
return this.buffers.has(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readFindText(): string {
|
public readFindText(): string {
|
||||||
@@ -156,15 +188,23 @@ class Clipboard {
|
|||||||
public writeText(value: string): Promise<void> {
|
public writeText(value: string): Promise<void> {
|
||||||
return clipboard.writeText(value);
|
return clipboard.writeText(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readText(): Promise<string> {
|
||||||
|
return clipboard.readText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeBuffer(format: string, buffer: Buffer): void {
|
||||||
|
this.buffers.set(format, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readBuffer(format: string): Buffer | undefined {
|
||||||
|
return this.buffers.get(format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Shell {
|
class Shell {
|
||||||
public async moveItemToTrash(path: string): Promise<void> {
|
public async moveItemToTrash(path: string): Promise<void> {
|
||||||
await client.evaluate((_helper, path) => {
|
await trash(path);
|
||||||
const trash = __non_webpack_require__("trash") as typeof import("trash");
|
|
||||||
|
|
||||||
return trash(path);
|
|
||||||
}, path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,14 +378,31 @@ class BrowserWindow extends EventEmitter {
|
|||||||
|
|
||||||
public setFullScreen(fullscreen: boolean): void {
|
public setFullScreen(fullscreen: boolean): void {
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
document.documentElement.requestFullscreen();
|
document.documentElement.requestFullscreen().catch((error) => {
|
||||||
|
logger.error(error.message);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
document.exitFullscreen();
|
document.exitFullscreen().catch((error) => {
|
||||||
|
logger.error(error.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isFullScreen(): boolean {
|
public isFullScreen(): boolean {
|
||||||
return document.fullscreenEnabled;
|
// TypeScript doesn't recognize this property.
|
||||||
|
// tslint:disable no-any
|
||||||
|
if (typeof (window as any)["fullScreen"] !== "undefined") {
|
||||||
|
return (window as any)["fullScreen"];
|
||||||
|
}
|
||||||
|
// tslint:enable no-any
|
||||||
|
|
||||||
|
try {
|
||||||
|
return window.matchMedia("(display-mode: fullscreen)").matches;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error.message);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isFocused(): boolean {
|
public isFocused(): boolean {
|
||||||
|
|||||||
@@ -1,763 +1,4 @@
|
|||||||
import { EventEmitter } from "events";
|
import { Module } from "@coder/protocol";
|
||||||
import * as fs from "fs";
|
|
||||||
import * as stream from "stream";
|
|
||||||
import { Client, IEncodingOptions, IEncodingOptionsCallback } from "@coder/protocol";
|
|
||||||
import { client } from "./client";
|
import { client } from "./client";
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
declare var __non_webpack_require__: typeof require;
|
export = client.modules[Module.Fs];
|
||||||
declare var _Buffer: typeof Buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements the native fs module
|
|
||||||
* Doesn't use `implements typeof import("fs")` to remove need for __promisify__ impls
|
|
||||||
*
|
|
||||||
* TODO: For now we can't use async in the evaluate calls because they get
|
|
||||||
* transpiled to TypeScript's helpers. tslib is included but we also need to set
|
|
||||||
* _this somehow which the __awaiter helper uses.
|
|
||||||
*/
|
|
||||||
class FS {
|
|
||||||
public constructor(
|
|
||||||
private readonly client: Client,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public access = (path: fs.PathLike, mode: number | undefined | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
if (typeof mode === "function") {
|
|
||||||
callback = mode;
|
|
||||||
mode = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, mode) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.access)(path, mode);
|
|
||||||
}, path, mode).then(() => {
|
|
||||||
callback!(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
public appendFile = (file: fs.PathLike | number, data: any, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, data, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.appendFile)(path, data, options);
|
|
||||||
}, file, data, options).then(() => {
|
|
||||||
callback!(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public chmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path, mode) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.chmod)(path, mode);
|
|
||||||
}, path, mode).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public chown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path, uid, gid) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.chown)(path, uid, gid);
|
|
||||||
}, path, uid, gid).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public close = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, fd) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.close)(fd);
|
|
||||||
}, fd).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public copyFile = (src: fs.PathLike, dest: fs.PathLike, flags: number | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
if (typeof flags === "function") {
|
|
||||||
callback = flags;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, src, dest, flags) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.copyFile)(src, dest, flags);
|
|
||||||
}, src, dest, typeof flags !== "function" ? flags : undefined).then(() => {
|
|
||||||
callback!(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
public createWriteStream = (path: fs.PathLike, options?: any): fs.WriteStream => {
|
|
||||||
const ae = this.client.run((ae, path, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const str = fs.createWriteStream(path, options);
|
|
||||||
ae.on("write", (d: string) => str.write(_Buffer.from(d, "utf8")));
|
|
||||||
ae.on("close", () => str.close());
|
|
||||||
ae.on("destroy", () => str.destroy());
|
|
||||||
str.on("close", () => ae.emit("close"));
|
|
||||||
str.on("open", (fd) => ae.emit("open", fd));
|
|
||||||
str.on("error", (err) => ae.emit(err));
|
|
||||||
|
|
||||||
return {
|
|
||||||
onDidDispose: (cb): fs.WriteStream => str.on("close", cb),
|
|
||||||
dispose: (): void => str.close(),
|
|
||||||
};
|
|
||||||
}, path, options);
|
|
||||||
|
|
||||||
return new (class WriteStream extends stream.Writable implements fs.WriteStream {
|
|
||||||
|
|
||||||
private _bytesWritten: number = 0;
|
|
||||||
|
|
||||||
public constructor() {
|
|
||||||
super({
|
|
||||||
write: (data, encoding, cb): void => {
|
|
||||||
this._bytesWritten += data.length;
|
|
||||||
ae.emit("write", Buffer.from(data, encoding), encoding);
|
|
||||||
cb();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ae.on("open", (fd: number) => this.emit("open", fd));
|
|
||||||
ae.on("close", () => this.emit("close"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public get bytesWritten(): number {
|
|
||||||
return this._bytesWritten;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get path(): string | Buffer {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public close(): void {
|
|
||||||
ae.emit("close");
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy(): void {
|
|
||||||
ae.emit("destroy");
|
|
||||||
}
|
|
||||||
|
|
||||||
}) as fs.WriteStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public exists = (path: fs.PathLike, callback: (exists: boolean) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.exists)(path);
|
|
||||||
}, path).then((r) => {
|
|
||||||
callback(r);
|
|
||||||
}).catch(() => {
|
|
||||||
callback(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public fchmod = (fd: number, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, fd, mode) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.fchmod)(fd, mode);
|
|
||||||
}, fd, mode).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public fchown = (fd: number, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, fd, uid, gid) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.fchown)(fd, uid, gid);
|
|
||||||
}, fd, uid, gid).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public fdatasync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, fd) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.fdatasync)(fd);
|
|
||||||
}, fd).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public fstat = (fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
|
||||||
this.client.evaluate((_helper, fd) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
const tslib = __non_webpack_require__("tslib") as typeof import("tslib");
|
|
||||||
|
|
||||||
return util.promisify(fs.fstat)(fd).then((stats) => {
|
|
||||||
return tslib.__assign(stats, {
|
|
||||||
_isBlockDevice: stats.isBlockDevice ? stats.isBlockDevice() : false,
|
|
||||||
_isCharacterDevice: stats.isCharacterDevice ? stats.isCharacterDevice() : false,
|
|
||||||
_isDirectory: stats.isDirectory(),
|
|
||||||
_isFIFO: stats.isFIFO ? stats.isFIFO() : false,
|
|
||||||
_isFile: stats.isFile(),
|
|
||||||
_isSocket: stats.isSocket ? stats.isSocket() : false,
|
|
||||||
_isSymbolicLink: stats.isSymbolicLink ? stats.isSymbolicLink() : false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, fd).then((stats) => {
|
|
||||||
callback(undefined!, new Stats(stats));
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public fsync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, fd) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.fsync)(fd);
|
|
||||||
}, fd).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ftruncate = (fd: number, len: number | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
if (typeof len === "function") {
|
|
||||||
callback = len;
|
|
||||||
len = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, fd, len) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.ftruncate)(fd, len);
|
|
||||||
}, fd, len).then(() => {
|
|
||||||
callback!(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public futimes = (fd: number, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, fd, atime, mtime) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.futimes)(fd, atime, mtime);
|
|
||||||
}, fd, atime, mtime).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public lchmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path, mode) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.lchmod)(path, mode);
|
|
||||||
}, path, mode).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public lchown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path, uid, gid) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.lchown)(path, uid, gid);
|
|
||||||
}, path, uid, gid).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public link = (existingPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, existingPath, newPath) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.link)(existingPath, newPath);
|
|
||||||
}, existingPath, newPath).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public lstat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
const tslib = __non_webpack_require__("tslib") as typeof import("tslib");
|
|
||||||
|
|
||||||
return util.promisify(fs.lstat)(path).then((stats) => {
|
|
||||||
return tslib.__assign(stats, {
|
|
||||||
_isBlockDevice: stats.isBlockDevice ? stats.isBlockDevice() : false,
|
|
||||||
_isCharacterDevice: stats.isCharacterDevice ? stats.isCharacterDevice() : false,
|
|
||||||
_isDirectory: stats.isDirectory(),
|
|
||||||
_isFIFO: stats.isFIFO ? stats.isFIFO() : false,
|
|
||||||
_isFile: stats.isFile(),
|
|
||||||
_isSocket: stats.isSocket ? stats.isSocket() : false,
|
|
||||||
_isSymbolicLink: stats.isSymbolicLink ? stats.isSymbolicLink() : false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, path).then((stats) => {
|
|
||||||
callback(undefined!, new Stats(stats));
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public mkdir = (path: fs.PathLike, mode: number | string | fs.MakeDirectoryOptions | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
if (typeof mode === "function") {
|
|
||||||
callback = mode;
|
|
||||||
mode = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, mode) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.mkdir)(path, mode);
|
|
||||||
}, path, mode).then(() => {
|
|
||||||
callback!(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public mkdtemp = (prefix: string, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, folder: string | Buffer) => void): void => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, prefix, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.mkdtemp)(prefix, options);
|
|
||||||
}, prefix, options).then((folder) => {
|
|
||||||
callback!(undefined!, folder);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public open = (path: fs.PathLike, flags: string | number, mode: string | number | undefined | null | ((err: NodeJS.ErrnoException, fd: number) => void), callback?: (err: NodeJS.ErrnoException, fd: number) => void): void => {
|
|
||||||
if (typeof mode === "function") {
|
|
||||||
callback = mode;
|
|
||||||
mode = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, flags, mode) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.open)(path, flags, mode);
|
|
||||||
}, path, flags, mode).then((fd) => {
|
|
||||||
callback!(undefined!, fd);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public read = <TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number, length: number, position: number | null, callback: (err: NodeJS.ErrnoException, bytesRead: number, buffer: TBuffer) => void): void => {
|
|
||||||
this.client.evaluate((_helper, fd, length, position) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
const buffer = new _Buffer(length);
|
|
||||||
|
|
||||||
return util.promisify(fs.read)(fd, buffer, 0, length, position).then((resp) => {
|
|
||||||
return {
|
|
||||||
bytesRead: resp.bytesRead,
|
|
||||||
content: resp.bytesRead < buffer.length ? buffer.slice(0, resp.bytesRead) : buffer,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, fd, length, position).then((resp) => {
|
|
||||||
buffer.set(resp.content, offset);
|
|
||||||
callback(undefined!, resp.bytesRead, resp.content as TBuffer);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex, undefined!, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public readFile = (path: fs.PathLike | number, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.readFile)(path, options).then((value) => value.toString());
|
|
||||||
}, path, options).then((buffer) => {
|
|
||||||
callback!(undefined!, buffer);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public readdir = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, files: Buffer[] | fs.Dirent[] | string[]) => void): void => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
// TODO: options can also take `withFileTypes` but the types aren't working.
|
|
||||||
this.client.evaluate((_helper, path, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.readdir)(path, options);
|
|
||||||
}, path, options).then((files) => {
|
|
||||||
callback!(undefined!, files);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public readlink = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, linkString: string | Buffer) => void): void => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.readlink)(path, options);
|
|
||||||
}, path, options).then((linkString) => {
|
|
||||||
callback!(undefined!, linkString);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public realpath = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, resolvedPath: string | Buffer) => void): void => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.realpath)(path, options);
|
|
||||||
}, path, options).then((resolvedPath) => {
|
|
||||||
callback!(undefined!, resolvedPath);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public rename = (oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, oldPath, newPath) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.rename)(oldPath, newPath);
|
|
||||||
}, oldPath, newPath).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public rmdir = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.rmdir)(path);
|
|
||||||
}, path).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public stat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
const tslib = __non_webpack_require__("tslib") as typeof import("tslib");
|
|
||||||
|
|
||||||
return util.promisify(fs.stat)(path).then((stats) => {
|
|
||||||
return tslib.__assign(stats, {
|
|
||||||
/**
|
|
||||||
* We need to check if functions exist because nexe's implemented FS
|
|
||||||
* lib doesnt implement fs.stats properly
|
|
||||||
*/
|
|
||||||
_isBlockDevice: stats.isBlockDevice ? stats.isBlockDevice() : false,
|
|
||||||
_isCharacterDevice: stats.isCharacterDevice ? stats.isCharacterDevice() : false,
|
|
||||||
_isDirectory: stats.isDirectory(),
|
|
||||||
_isFIFO: stats.isFIFO ? stats.isFIFO() : false,
|
|
||||||
_isFile: stats.isFile(),
|
|
||||||
_isSocket: stats.isSocket ? stats.isSocket() : false,
|
|
||||||
_isSymbolicLink: stats.isSymbolicLink ? stats.isSymbolicLink() : false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, path).then((stats) => {
|
|
||||||
callback(undefined!, new Stats(stats));
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public symlink = (target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
if (typeof type === "function") {
|
|
||||||
callback = type;
|
|
||||||
type = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, target, path, type) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.symlink)(target, path, type);
|
|
||||||
}, target, path, type).then(() => {
|
|
||||||
callback!(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public truncate = (path: fs.PathLike, len: number | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
if (typeof len === "function") {
|
|
||||||
callback = len;
|
|
||||||
len = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, len) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.truncate)(path, len);
|
|
||||||
}, path, len).then(() => {
|
|
||||||
callback!(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public unlink = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.unlink)(path);
|
|
||||||
}, path).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public utimes = (path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
this.client.evaluate((_helper, path, atime, mtime) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.utimes)(path, atime, mtime);
|
|
||||||
}, path, atime, mtime).then(() => {
|
|
||||||
callback(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public write = <TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void), length: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void), position: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void), callback?: (err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void): void => {
|
|
||||||
if (typeof offset === "function") {
|
|
||||||
callback = offset;
|
|
||||||
offset = undefined;
|
|
||||||
}
|
|
||||||
if (typeof length === "function") {
|
|
||||||
callback = length;
|
|
||||||
length = undefined;
|
|
||||||
}
|
|
||||||
if (typeof position === "function") {
|
|
||||||
callback = position;
|
|
||||||
position = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, fd, buffer, offset, length, position) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.write)(fd, _Buffer.from(buffer, "utf8"), offset, length, position).then((resp) => {
|
|
||||||
return {
|
|
||||||
bytesWritten: resp.bytesWritten,
|
|
||||||
content: resp.buffer.toString("utf8"),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, fd, buffer.toString(), offset, length, position).then((r) => {
|
|
||||||
callback!(undefined!, r.bytesWritten, Buffer.from(r.content, "utf8") as TBuffer);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex, undefined!, undefined!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
public writeFile = (path: fs.PathLike | number, data: any, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException) => void): void => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
callback = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
this.client.evaluate((_helper, path, data, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
|
||||||
const util = __non_webpack_require__("util") as typeof import("util");
|
|
||||||
|
|
||||||
return util.promisify(fs.writeFile)(path, data, options);
|
|
||||||
}, path, data, options).then(() => {
|
|
||||||
callback!(undefined!);
|
|
||||||
}).catch((ex) => {
|
|
||||||
callback!(ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public watch = (filename: fs.PathLike, options?: IEncodingOptions | ((event: string, filename: string | Buffer) => void), listener?: ((event: string, filename: string | Buffer) => void)): fs.FSWatcher => {
|
|
||||||
if (typeof options === "function") {
|
|
||||||
listener = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ae = this.client.run((ae, filename, hasListener, options) => {
|
|
||||||
const fs = __non_webpack_require__("fs") as typeof import ("fs");
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
const watcher = fs.watch(filename, options as any, hasListener ? (event, filename): void => {
|
|
||||||
ae.emit("listener", event, filename);
|
|
||||||
} : undefined);
|
|
||||||
watcher.on("change", (event, filename) => ae.emit("change", event, filename));
|
|
||||||
watcher.on("error", (error) => ae.emit("error", error));
|
|
||||||
ae.on("close", () => watcher.close());
|
|
||||||
|
|
||||||
return {
|
|
||||||
onDidDispose: (cb): void => ae.on("close", cb),
|
|
||||||
dispose: (): void => watcher.close(),
|
|
||||||
};
|
|
||||||
}, filename.toString(), !!listener, options);
|
|
||||||
|
|
||||||
return new class Watcher extends EventEmitter implements fs.FSWatcher {
|
|
||||||
public constructor() {
|
|
||||||
super();
|
|
||||||
ae.on("change", (event: string, filename: string) => this.emit("change", event, filename));
|
|
||||||
ae.on("error", (error: Error) => this.emit("error", error));
|
|
||||||
ae.on("listener", (event: string, filename: string) => listener && listener(event, filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
public close(): void {
|
|
||||||
ae.emit("close");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IStats {
|
|
||||||
dev: number;
|
|
||||||
ino: number;
|
|
||||||
mode: number;
|
|
||||||
nlink: number;
|
|
||||||
uid: number;
|
|
||||||
gid: number;
|
|
||||||
rdev: number;
|
|
||||||
size: number;
|
|
||||||
blksize: number;
|
|
||||||
blocks: number;
|
|
||||||
atimeMs: number;
|
|
||||||
mtimeMs: number;
|
|
||||||
ctimeMs: number;
|
|
||||||
birthtimeMs: number;
|
|
||||||
atime: Date | string;
|
|
||||||
mtime: Date | string;
|
|
||||||
ctime: Date | string;
|
|
||||||
birthtime: Date | string;
|
|
||||||
_isFile: boolean;
|
|
||||||
_isDirectory: boolean;
|
|
||||||
_isBlockDevice: boolean;
|
|
||||||
_isCharacterDevice: boolean;
|
|
||||||
_isSymbolicLink: boolean;
|
|
||||||
_isFIFO: boolean;
|
|
||||||
_isSocket: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Stats implements fs.Stats {
|
|
||||||
public readonly atime: Date;
|
|
||||||
public readonly mtime: Date;
|
|
||||||
public readonly ctime: Date;
|
|
||||||
public readonly birthtime: Date;
|
|
||||||
|
|
||||||
public constructor(private readonly stats: IStats) {
|
|
||||||
this.atime = new Date(stats.atime);
|
|
||||||
this.mtime = new Date(stats.mtime);
|
|
||||||
this.ctime = new Date(stats.ctime);
|
|
||||||
this.birthtime = new Date(stats.birthtime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get dev(): number { return this.stats.dev; }
|
|
||||||
public get ino(): number { return this.stats.ino; }
|
|
||||||
public get mode(): number { return this.stats.mode; }
|
|
||||||
public get nlink(): number { return this.stats.nlink; }
|
|
||||||
public get uid(): number { return this.stats.uid; }
|
|
||||||
public get gid(): number { return this.stats.gid; }
|
|
||||||
public get rdev(): number { return this.stats.rdev; }
|
|
||||||
public get size(): number { return this.stats.size; }
|
|
||||||
public get blksize(): number { return this.stats.blksize; }
|
|
||||||
public get blocks(): number { return this.stats.blocks; }
|
|
||||||
public get atimeMs(): number { return this.stats.atimeMs; }
|
|
||||||
public get mtimeMs(): number { return this.stats.mtimeMs; }
|
|
||||||
public get ctimeMs(): number { return this.stats.ctimeMs; }
|
|
||||||
public get birthtimeMs(): number { return this.stats.birthtimeMs; }
|
|
||||||
public isFile(): boolean { return this.stats._isFile; }
|
|
||||||
public isDirectory(): boolean { return this.stats._isDirectory; }
|
|
||||||
public isBlockDevice(): boolean { return this.stats._isBlockDevice; }
|
|
||||||
public isCharacterDevice(): boolean { return this.stats._isCharacterDevice; }
|
|
||||||
public isSymbolicLink(): boolean { return this.stats._isSymbolicLink; }
|
|
||||||
public isFIFO(): boolean { return this.stats._isFIFO; }
|
|
||||||
public isSocket(): boolean { return this.stats._isSocket; }
|
|
||||||
|
|
||||||
public toObject(): object {
|
|
||||||
return JSON.parse(JSON.stringify(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fillFs = new FS(client);
|
|
||||||
// Methods that don't follow the standard callback pattern (an error followed
|
|
||||||
// by a single result) need to provide a custom promisify function.
|
|
||||||
Object.defineProperty(fillFs.exists, promisify.custom, {
|
|
||||||
value: (path: fs.PathLike): Promise<boolean> => new Promise((resolve): void => fillFs.exists(path, resolve)),
|
|
||||||
});
|
|
||||||
export = fillFs;
|
|
||||||
|
|||||||
@@ -1,258 +1,4 @@
|
|||||||
import * as net from "net";
|
import { Module } from "@coder/protocol";
|
||||||
import { CallbackEmitter, ActiveEvalDuplex, ActiveEvalHelper } from "@coder/protocol";
|
|
||||||
import { client } from "./client";
|
import { client } from "./client";
|
||||||
|
|
||||||
declare var __non_webpack_require__: typeof require;
|
export = client.modules[Module.Net];
|
||||||
|
|
||||||
class Socket extends ActiveEvalDuplex implements net.Socket {
|
|
||||||
private _connecting: boolean = false;
|
|
||||||
private _destroyed: boolean = false;
|
|
||||||
|
|
||||||
public constructor(options?: net.SocketConstructorOpts, ae?: ActiveEvalHelper) {
|
|
||||||
super(ae || client.run((ae, options) => {
|
|
||||||
const net = __non_webpack_require__("net") as typeof import("net");
|
|
||||||
|
|
||||||
return ae.bindSocket(new net.Socket(options));
|
|
||||||
}, options));
|
|
||||||
|
|
||||||
this.ae.on("connect", () => {
|
|
||||||
this._connecting = false;
|
|
||||||
this.emit("connect");
|
|
||||||
});
|
|
||||||
this.ae.on("error", () => {
|
|
||||||
this._connecting = false;
|
|
||||||
this._destroyed = true;
|
|
||||||
});
|
|
||||||
this.ae.on("lookup", (error, address, family, host) => this.emit("lookup", error, address, family, host));
|
|
||||||
this.ae.on("timeout", () => this.emit("timeout"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public connect(options: net.SocketConnectOpts | number | string, host?: string | Function, connectionListener?: Function): this {
|
|
||||||
// This is to get around type issues with socket.connect as well as extract
|
|
||||||
// the function wherever it might be.
|
|
||||||
switch (typeof options) {
|
|
||||||
case "string": options = { path: options }; break;
|
|
||||||
case "number": options = { port: options }; break;
|
|
||||||
}
|
|
||||||
switch (typeof host) {
|
|
||||||
case "function": connectionListener = host; break;
|
|
||||||
case "string": (options as net.TcpSocketConnectOpts).host = host; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._connecting = true;
|
|
||||||
this.ae.emit("connect", options, this.storeCallback(connectionListener));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
public write(data: any, encoding?: string | Function, fd?: string | Function): boolean {
|
|
||||||
let callback: Function | undefined;
|
|
||||||
if (typeof encoding === "function") {
|
|
||||||
callback = encoding;
|
|
||||||
encoding = undefined;
|
|
||||||
}
|
|
||||||
if (typeof fd === "function") {
|
|
||||||
callback = fd;
|
|
||||||
fd = undefined;
|
|
||||||
}
|
|
||||||
this.ae.emit("write", data, encoding, fd, this.storeCallback(callback));
|
|
||||||
|
|
||||||
return true; // Always true since we can't get this synchronously.
|
|
||||||
}
|
|
||||||
|
|
||||||
public get connecting(): boolean { return this._connecting; }
|
|
||||||
public get destroyed(): boolean { return this._destroyed; }
|
|
||||||
|
|
||||||
public get bufferSize(): number { throw new Error("not implemented"); }
|
|
||||||
public get bytesRead(): number { throw new Error("not implemented"); }
|
|
||||||
public get bytesWritten(): number { throw new Error("not implemented"); }
|
|
||||||
public get localAddress(): string { throw new Error("not implemented"); }
|
|
||||||
public get localPort(): number { throw new Error("not implemented"); }
|
|
||||||
public address(): net.AddressInfo | string { throw new Error("not implemented"); }
|
|
||||||
|
|
||||||
public setTimeout(timeout: number, callback?: Function): this { return this.emitReturnThis("setTimeout", timeout, this.storeCallback(callback)); }
|
|
||||||
public setNoDelay(noDelay?: boolean): this { return this.emitReturnThis("setNoDelay", noDelay); }
|
|
||||||
public setKeepAlive(enable?: boolean, initialDelay?: number): this { return this.emitReturnThis("setKeepAlive", enable, initialDelay); }
|
|
||||||
public unref(): void { this.ae.emit("unref"); }
|
|
||||||
public ref(): void { this.ae.emit("ref"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
class Server extends CallbackEmitter implements net.Server {
|
|
||||||
private readonly sockets = new Map<number, Socket>();
|
|
||||||
private _listening: boolean = false;
|
|
||||||
|
|
||||||
public constructor(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: Socket) => void), connectionListener?: (socket: Socket) => void) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
if (typeof options === "function") {
|
|
||||||
connectionListener = options;
|
|
||||||
options = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ae = client.run((ae, options, callbackId) => {
|
|
||||||
const net = __non_webpack_require__("net") as typeof import("net");
|
|
||||||
|
|
||||||
let connectionId = 0;
|
|
||||||
const sockets = new Map<number, net.Socket>();
|
|
||||||
const storeSocket = (socket: net.Socket): number => {
|
|
||||||
const socketId = connectionId++;
|
|
||||||
sockets.set(socketId, socket);
|
|
||||||
const socketAe = ae.createUnique(socketId);
|
|
||||||
const disposer = socketAe.bindSocket(socket);
|
|
||||||
socket.on("close", () => {
|
|
||||||
disposer.dispose();
|
|
||||||
sockets.delete(socketId);
|
|
||||||
});
|
|
||||||
|
|
||||||
return socketId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const callback = ae.maybeCallback(callbackId);
|
|
||||||
let server = new net.Server(options, typeof callback !== "undefined" ? (socket): void => {
|
|
||||||
callback(storeSocket(socket));
|
|
||||||
} : undefined);
|
|
||||||
|
|
||||||
server.on("close", () => ae.emit("close"));
|
|
||||||
server.on("connection", (socket) => ae.emit("connection", storeSocket(socket)));
|
|
||||||
server.on("error", (error) => ae.emit("error", error));
|
|
||||||
server.on("listening", () => ae.emit("listening"));
|
|
||||||
|
|
||||||
ae.on("close", (callbackId: number) => server.close(ae.maybeCallback(callbackId)));
|
|
||||||
ae.on("listen", (handle?: net.ListenOptions | number | string) => server.listen(handle));
|
|
||||||
ae.on("ref", () => server.ref());
|
|
||||||
ae.on("unref", () => server.unref());
|
|
||||||
|
|
||||||
return {
|
|
||||||
onDidDispose: (cb): net.Server => server.on("close", cb),
|
|
||||||
dispose: (): void => {
|
|
||||||
server.removeAllListeners();
|
|
||||||
server.close();
|
|
||||||
sockets.forEach((socket) => {
|
|
||||||
socket.removeAllListeners();
|
|
||||||
socket.end();
|
|
||||||
socket.destroy();
|
|
||||||
socket.unref();
|
|
||||||
});
|
|
||||||
sockets.clear();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, options || {}, this.storeCallback(connectionListener));
|
|
||||||
|
|
||||||
this.ae.on("close", () => {
|
|
||||||
this._listening = false;
|
|
||||||
this.emit("close");
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ae.on("connection", (socketId) => {
|
|
||||||
const socketAe = this.ae.createUnique(socketId);
|
|
||||||
const socket = new Socket(undefined, socketAe);
|
|
||||||
this.sockets.set(socketId, socket);
|
|
||||||
socket.on("close", () => this.sockets.delete(socketId));
|
|
||||||
if (connectionListener) {
|
|
||||||
connectionListener(socket);
|
|
||||||
}
|
|
||||||
this.emit("connection", socket);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ae.on("error", (error) => {
|
|
||||||
this._listening = false;
|
|
||||||
this.emit("error", error);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ae.on("listening", () => {
|
|
||||||
this._listening = true;
|
|
||||||
this.emit("listening");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public listen(handle?: net.ListenOptions | number | string, hostname?: string | number | Function, backlog?: number | Function, listeningListener?: Function): this {
|
|
||||||
if (typeof handle === "undefined") {
|
|
||||||
throw new Error("no handle");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (typeof handle) {
|
|
||||||
case "number": handle = { port: handle }; break;
|
|
||||||
case "string": handle = { path: handle }; break;
|
|
||||||
}
|
|
||||||
switch (typeof hostname) {
|
|
||||||
case "function": listeningListener = hostname; break;
|
|
||||||
case "string": handle.host = hostname; break;
|
|
||||||
case "number": handle.backlog = hostname; break;
|
|
||||||
}
|
|
||||||
switch (typeof backlog) {
|
|
||||||
case "function": listeningListener = backlog; break;
|
|
||||||
case "number": handle.backlog = backlog; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listeningListener) {
|
|
||||||
this.ae.on("listening", () => {
|
|
||||||
listeningListener!();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ae.emit("listen", handle);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public close(callback?: Function): this {
|
|
||||||
// close() doesn't fire the close event until all connections are also
|
|
||||||
// closed, but it does prevent new connections.
|
|
||||||
this._listening = false;
|
|
||||||
this.ae.emit("close", this.storeCallback(callback));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get connections(): number { return this.sockets.size; }
|
|
||||||
public get listening(): boolean { return this._listening; }
|
|
||||||
|
|
||||||
public get maxConnections(): number { throw new Error("not implemented"); }
|
|
||||||
public address(): net.AddressInfo | string { throw new Error("not implemented"); }
|
|
||||||
|
|
||||||
public ref(): this { return this.emitReturnThis("ref"); }
|
|
||||||
public unref(): this { return this.emitReturnThis("unref"); }
|
|
||||||
public getConnections(cb: (error: Error | null, count: number) => void): void { cb(null, this.sockets.size); }
|
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
private emitReturnThis(event: string, ...args: any[]): this {
|
|
||||||
this.ae.emit(event, ...args);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeNet = typeof net;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of net for the browser.
|
|
||||||
*/
|
|
||||||
class Net implements NodeNet {
|
|
||||||
// @ts-ignore this is because Socket is missing things from the Stream
|
|
||||||
// namespace but I'm unsure how best to provide them (finished,
|
|
||||||
// finished.__promisify__, pipeline, and some others) or if it even matters.
|
|
||||||
public readonly Socket = Socket;
|
|
||||||
public readonly Server = Server;
|
|
||||||
|
|
||||||
public createConnection(target: string | number | net.NetConnectOpts, host?: string | Function, callback?: Function): net.Socket {
|
|
||||||
const socket = new Socket();
|
|
||||||
socket.connect(target, host, callback);
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public createServer(
|
|
||||||
options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
|
|
||||||
connectionListener?: (socket: net.Socket) => void,
|
|
||||||
): net.Server {
|
|
||||||
return new Server(options, connectionListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public connect(): net.Socket { throw new Error("not implemented"); }
|
|
||||||
public isIP(_input: string): number { throw new Error("not implemented"); }
|
|
||||||
public isIPv4(_input: string): boolean { throw new Error("not implemented"); }
|
|
||||||
public isIPv6(_input: string): boolean { throw new Error("not implemented"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
export = new Net();
|
|
||||||
|
|||||||
4
packages/ide/src/fill/trash.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { Module } from "@coder/protocol";
|
||||||
|
import { client } from "./client";
|
||||||
|
|
||||||
|
export = client.modules[Module.Trash].trash;
|
||||||
8
packages/ide/src/fill/webview.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" style="width: 100%; height: 100%">
|
||||||
|
<head>
|
||||||
|
<title>Virtual Document</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; overflow: hidden; width: 100%; height: 100%">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,14 +1,64 @@
|
|||||||
import { logger, field } from "@coder/logger";
|
import { logger, field } from "@coder/logger";
|
||||||
import { NotificationService, INotificationHandle, INotificationService, Severity } from "./fill/notification";
|
import { NotificationService, INotificationHandle, INotificationService, Severity } from "./fill/notification";
|
||||||
|
|
||||||
|
// tslint:disable no-any can have different return values
|
||||||
|
|
||||||
interface IRetryItem {
|
interface IRetryItem {
|
||||||
|
/**
|
||||||
|
* How many times this item has been retried.
|
||||||
|
*/
|
||||||
count?: number;
|
count?: number;
|
||||||
delay?: number; // In seconds.
|
|
||||||
end?: number; // In ms.
|
/**
|
||||||
fn(): any | Promise<any>; // tslint:disable-line no-any can have different return values
|
* In seconds.
|
||||||
|
*/
|
||||||
|
delay?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In milliseconds.
|
||||||
|
*/
|
||||||
|
end?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to run when retrying.
|
||||||
|
*/
|
||||||
|
fn(): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer for running this item.
|
||||||
|
*/
|
||||||
timeout?: number | NodeJS.Timer;
|
timeout?: number | NodeJS.Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the item is retrying or waiting to retry.
|
||||||
|
*/
|
||||||
running?: boolean;
|
running?: boolean;
|
||||||
showInNotification: boolean;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An retry-able instance.
|
||||||
|
*/
|
||||||
|
export interface RetryInstance {
|
||||||
|
/**
|
||||||
|
* Run this retry.
|
||||||
|
*/
|
||||||
|
run(error?: Error): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block on this instance.
|
||||||
|
*/
|
||||||
|
block(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A retry-able instance that doesn't use a promise so it must be manually
|
||||||
|
* ran again on failure and recovered on success.
|
||||||
|
*/
|
||||||
|
export interface ManualRetryInstance extends RetryInstance {
|
||||||
|
/**
|
||||||
|
* Mark this item as recovered.
|
||||||
|
*/
|
||||||
|
recover(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,11 +71,11 @@ interface IRetryItem {
|
|||||||
* to the user explaining what is happening with an option to immediately retry.
|
* to the user explaining what is happening with an option to immediately retry.
|
||||||
*/
|
*/
|
||||||
export class Retry {
|
export class Retry {
|
||||||
private items = new Map<string, IRetryItem>();
|
private readonly items = new Map<string, IRetryItem>();
|
||||||
|
|
||||||
// Times are in seconds.
|
// Times are in seconds.
|
||||||
private readonly retryMinDelay = 1;
|
private readonly retryMinDelay = 1;
|
||||||
private readonly retryMaxDelay = 10;
|
private readonly retryMaxDelay = 3;
|
||||||
private readonly maxImmediateRetries = 5;
|
private readonly maxImmediateRetries = 5;
|
||||||
private readonly retryExponent = 1.5;
|
private readonly retryExponent = 1.5;
|
||||||
private blocked: string | boolean | undefined;
|
private blocked: string | boolean | undefined;
|
||||||
@@ -50,8 +100,49 @@ export class Retry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block retries when we know they will fail (for example when starting Wush
|
* Register a function to retry that starts/connects to a service.
|
||||||
* back up). If a name is passed, that service will still be allowed to retry
|
*
|
||||||
|
* The service is automatically retried or recovered when the promise resolves
|
||||||
|
* or rejects. If the service dies after starting, it must be manually
|
||||||
|
* retried.
|
||||||
|
*/
|
||||||
|
public register(name: string, fn: () => Promise<any>): RetryInstance;
|
||||||
|
/**
|
||||||
|
* Register a function to retry that starts/connects to a service.
|
||||||
|
*
|
||||||
|
* Must manually retry if it fails to start again or dies after restarting and
|
||||||
|
* manually recover if it succeeds in starting again.
|
||||||
|
*/
|
||||||
|
public register(name: string, fn: () => any): ManualRetryInstance;
|
||||||
|
/**
|
||||||
|
* Register a function to retry that starts/connects to a service.
|
||||||
|
*/
|
||||||
|
public register(name: string, fn: () => any): RetryInstance | ManualRetryInstance {
|
||||||
|
if (this.items.has(name)) {
|
||||||
|
throw new Error(`"${name}" is already registered`);
|
||||||
|
}
|
||||||
|
this.items.set(name, { fn });
|
||||||
|
|
||||||
|
return {
|
||||||
|
block: (): void => this.block(name),
|
||||||
|
run: (error?: Error): void => this.run(name, error),
|
||||||
|
recover: (): void => this.recover(name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Un-register a function to retry.
|
||||||
|
*/
|
||||||
|
public unregister(name: string): void {
|
||||||
|
if (!this.items.has(name)) {
|
||||||
|
throw new Error(`"${name}" is not registered`);
|
||||||
|
}
|
||||||
|
this.items.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block retries when we know they will fail (for example when the socket is
|
||||||
|
* down ). If a name is passed, that service will still be allowed to retry
|
||||||
* (unless we have already blocked).
|
* (unless we have already blocked).
|
||||||
*
|
*
|
||||||
* Blocking without a name will override a block with a name.
|
* Blocking without a name will override a block with a name.
|
||||||
@@ -68,7 +159,7 @@ export class Retry {
|
|||||||
/**
|
/**
|
||||||
* Unblock retries and run any that are pending.
|
* Unblock retries and run any that are pending.
|
||||||
*/
|
*/
|
||||||
public unblock(): void {
|
private unblock(): void {
|
||||||
this.blocked = false;
|
this.blocked = false;
|
||||||
this.items.forEach((item, name) => {
|
this.items.forEach((item, name) => {
|
||||||
if (item.running) {
|
if (item.running) {
|
||||||
@@ -77,35 +168,10 @@ export class Retry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a function to retry that starts/connects to a service.
|
|
||||||
*
|
|
||||||
* If the function returns a promise, it will automatically be retried,
|
|
||||||
* recover, & unblock after calling `run` once (otherwise they need to be
|
|
||||||
* called manually).
|
|
||||||
*/
|
|
||||||
// tslint:disable-next-line no-any can have different return values
|
|
||||||
public register(name: string, fn: () => any | Promise<any>, showInNotification: boolean = true): void {
|
|
||||||
if (this.items.has(name)) {
|
|
||||||
throw new Error(`"${name}" is already registered`);
|
|
||||||
}
|
|
||||||
this.items.set(name, { fn, showInNotification });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregister a function to retry.
|
|
||||||
*/
|
|
||||||
public unregister(name: string): void {
|
|
||||||
if (!this.items.has(name)) {
|
|
||||||
throw new Error(`"${name}" is not registered`);
|
|
||||||
}
|
|
||||||
this.items.delete(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry a service.
|
* Retry a service.
|
||||||
*/
|
*/
|
||||||
public run(name: string, error?: Error): void {
|
private run(name: string, error?: Error): void {
|
||||||
if (!this.items.has(name)) {
|
if (!this.items.has(name)) {
|
||||||
throw new Error(`"${name}" is not registered`);
|
throw new Error(`"${name}" is not registered`);
|
||||||
}
|
}
|
||||||
@@ -149,7 +215,7 @@ export class Retry {
|
|||||||
/**
|
/**
|
||||||
* Reset a service after a successfully recovering.
|
* Reset a service after a successfully recovering.
|
||||||
*/
|
*/
|
||||||
public recover(name: string): void {
|
private recover(name: string): void {
|
||||||
if (!this.items.has(name)) {
|
if (!this.items.has(name)) {
|
||||||
throw new Error(`"${name}" is not registered`);
|
throw new Error(`"${name}" is not registered`);
|
||||||
}
|
}
|
||||||
@@ -191,9 +257,9 @@ export class Retry {
|
|||||||
if (this.blocked === name) {
|
if (this.blocked === name) {
|
||||||
this.unblock();
|
this.unblock();
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch((error) => {
|
||||||
endItem();
|
endItem();
|
||||||
this.run(name);
|
this.run(name, error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
endItem();
|
endItem();
|
||||||
@@ -214,8 +280,7 @@ export class Retry {
|
|||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const items = Array.from(this.items.entries()).filter(([_, item]) => {
|
const items = Array.from(this.items.entries()).filter(([_, item]) => {
|
||||||
return item.showInNotification
|
return typeof item.end !== "undefined"
|
||||||
&& typeof item.end !== "undefined"
|
|
||||||
&& item.end > now
|
&& item.end > now
|
||||||
&& item.delay && item.delay >= this.notificationThreshold;
|
&& item.delay && item.delay >= this.notificationThreshold;
|
||||||
}).sort((a, b) => {
|
}).sort((a, b) => {
|
||||||
|
|||||||
@@ -2,113 +2,3 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@types/events@*":
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
|
||||||
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
|
|
||||||
|
|
||||||
"@types/glob@*":
|
|
||||||
version "7.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
|
|
||||||
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
|
|
||||||
dependencies:
|
|
||||||
"@types/events" "*"
|
|
||||||
"@types/minimatch" "*"
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@types/minimatch@*":
|
|
||||||
version "3.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
|
||||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
|
||||||
|
|
||||||
"@types/node@*":
|
|
||||||
version "11.9.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.4.tgz#ceb0048a546db453f6248f2d1d95e937a6f00a14"
|
|
||||||
integrity sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==
|
|
||||||
|
|
||||||
"@types/rimraf@^2.0.2":
|
|
||||||
version "2.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e"
|
|
||||||
integrity sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ==
|
|
||||||
dependencies:
|
|
||||||
"@types/glob" "*"
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
|
||||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
|
||||||
version "1.1.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
|
||||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
|
||||||
dependencies:
|
|
||||||
balanced-match "^1.0.0"
|
|
||||||
concat-map "0.0.1"
|
|
||||||
|
|
||||||
concat-map@0.0.1:
|
|
||||||
version "0.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
|
||||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
|
||||||
|
|
||||||
fs.realpath@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
|
||||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
|
||||||
|
|
||||||
glob@^7.1.3:
|
|
||||||
version "7.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
|
||||||
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
|
||||||
dependencies:
|
|
||||||
fs.realpath "^1.0.0"
|
|
||||||
inflight "^1.0.4"
|
|
||||||
inherits "2"
|
|
||||||
minimatch "^3.0.4"
|
|
||||||
once "^1.3.0"
|
|
||||||
path-is-absolute "^1.0.0"
|
|
||||||
|
|
||||||
inflight@^1.0.4:
|
|
||||||
version "1.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
|
||||||
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
|
||||||
dependencies:
|
|
||||||
once "^1.3.0"
|
|
||||||
wrappy "1"
|
|
||||||
|
|
||||||
inherits@2:
|
|
||||||
version "2.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
|
||||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
|
||||||
|
|
||||||
minimatch@^3.0.4:
|
|
||||||
version "3.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
|
||||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
|
||||||
dependencies:
|
|
||||||
brace-expansion "^1.1.7"
|
|
||||||
|
|
||||||
once@^1.3.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
|
||||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
|
||||||
dependencies:
|
|
||||||
wrappy "1"
|
|
||||||
|
|
||||||
path-is-absolute@^1.0.0:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
|
||||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
|
||||||
|
|
||||||
rimraf@^2.6.3:
|
|
||||||
version "2.6.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
|
||||||
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
|
|
||||||
dependencies:
|
|
||||||
glob "^7.1.3"
|
|
||||||
|
|
||||||
wrappy@1:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
|
||||||
|
|||||||
@@ -5,9 +5,12 @@
|
|||||||
"build": "tsc -p tsconfig.build.json && cp ./out/packages/logger/src/* ./out && rm -rf out/packages && ../../node_modules/.bin/webpack --config ./webpack.config.js",
|
"build": "tsc -p tsconfig.build.json && cp ./out/packages/logger/src/* ./out && rm -rf out/packages && ../../node_modules/.bin/webpack --config ./webpack.config.js",
|
||||||
"postinstall": "if [ ! -d out ];then npm run build; fi"
|
"postinstall": "if [ ! -d out ];then npm run build; fi"
|
||||||
},
|
},
|
||||||
"version": "1.0.3",
|
"version": "1.1.3",
|
||||||
"main": "out/main.js",
|
"main": "out/main.js",
|
||||||
"types": "out/index.d.ts",
|
"types": "out/index.d.ts",
|
||||||
"author": "Coder",
|
"author": "Coder",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@google-cloud/logging": "^4.5.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
packages/logger/src/extender.test.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { field, logger } from "./logger";
|
||||||
|
import { createStackdriverExtender } from "./extender";
|
||||||
|
|
||||||
|
describe("Extender", () => {
|
||||||
|
it("should add stackdriver extender", () => {
|
||||||
|
logger.extend(createStackdriverExtender("coder-dev-1", "logging-package-tests"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log", async () => {
|
||||||
|
logger.debug("Bananas!", field("frog", { hi: "wow" }));
|
||||||
|
});
|
||||||
|
});
|
||||||
63
packages/logger/src/extender.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import * as gcl from "@google-cloud/logging";
|
||||||
|
import { Extender, logger, field } from "./logger";
|
||||||
|
|
||||||
|
export const createStackdriverExtender = (projectId: string, logId: string): Extender => {
|
||||||
|
enum GcpLogSeverity {
|
||||||
|
DEFAULT = 0,
|
||||||
|
DEBUG = 100,
|
||||||
|
INFO = 200,
|
||||||
|
NOTICE = 300,
|
||||||
|
WARNING = 400,
|
||||||
|
ERROR = 500,
|
||||||
|
CRITICAL = 600,
|
||||||
|
ALERT = 700,
|
||||||
|
EMERGENCY = 800,
|
||||||
|
}
|
||||||
|
|
||||||
|
const logging = new gcl.Logging({
|
||||||
|
autoRetry: true,
|
||||||
|
projectId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const log = logging.log(logId);
|
||||||
|
const convertSeverity = (severity: "trace" | "info" | "warn" | "debug" | "error"): GcpLogSeverity => {
|
||||||
|
switch (severity) {
|
||||||
|
case "trace":
|
||||||
|
case "debug":
|
||||||
|
return GcpLogSeverity.DEBUG;
|
||||||
|
case "info":
|
||||||
|
return GcpLogSeverity.INFO;
|
||||||
|
case "error":
|
||||||
|
return GcpLogSeverity.ERROR;
|
||||||
|
case "warn":
|
||||||
|
return GcpLogSeverity.WARNING;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (options): void => {
|
||||||
|
const severity = convertSeverity(options.type);
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
const metadata = {} as any;
|
||||||
|
if (options.fields) {
|
||||||
|
options.fields.forEach((f) => {
|
||||||
|
if (!f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
metadata[f.identifier] = f.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = log.entry({
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
severity: severity as any,
|
||||||
|
}, {
|
||||||
|
...metadata,
|
||||||
|
message: options.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
log.write(entry).catch((ex) => {
|
||||||
|
logger.named("GCP").error("Failed to log", field("error", ex));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
@@ -59,6 +59,14 @@ export const field = <T>(name: string, value: T): Field<T> => {
|
|||||||
return new Field(name, value);
|
return new Field(name, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Extender = (msg: {
|
||||||
|
message: string,
|
||||||
|
level: Level,
|
||||||
|
type: "trace" | "info" | "warn" | "debug" | "error",
|
||||||
|
fields?: FieldArray,
|
||||||
|
section?: string,
|
||||||
|
}) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This formats & builds text for logging.
|
* This formats & builds text for logging.
|
||||||
* It should only be used to build one log item at a time since it stores the
|
* It should only be used to build one log item at a time since it stores the
|
||||||
@@ -221,6 +229,7 @@ export class Logger {
|
|||||||
private _formatter: Formatter,
|
private _formatter: Formatter,
|
||||||
private readonly name?: string,
|
private readonly name?: string,
|
||||||
private readonly defaultFields?: FieldArray,
|
private readonly defaultFields?: FieldArray,
|
||||||
|
private readonly extenders: Extender[] = [],
|
||||||
) {
|
) {
|
||||||
if (name) {
|
if (name) {
|
||||||
this.nameColor = this.hashStringToColor(name);
|
this.nameColor = this.hashStringToColor(name);
|
||||||
@@ -248,6 +257,10 @@ export class Logger {
|
|||||||
this.muted = true;
|
this.muted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extend(extender: Extender): void {
|
||||||
|
this.extenders.push(extender);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outputs information.
|
* Outputs information.
|
||||||
*/
|
*/
|
||||||
@@ -328,7 +341,7 @@ export class Logger {
|
|||||||
* Each name is deterministically generated a color.
|
* Each name is deterministically generated a color.
|
||||||
*/
|
*/
|
||||||
public named(name: string, ...fields: FieldArray): Logger {
|
public named(name: string, ...fields: FieldArray): Logger {
|
||||||
const l = new Logger(this._formatter, name, fields);
|
const l = new Logger(this._formatter, name, fields, this.extenders);
|
||||||
if (this.muted) {
|
if (this.muted) {
|
||||||
l.mute();
|
l.mute();
|
||||||
}
|
}
|
||||||
@@ -393,6 +406,16 @@ export class Logger {
|
|||||||
console.log(...this._formatter.flush());
|
console.log(...this._formatter.flush());
|
||||||
}
|
}
|
||||||
// tslint:enable no-console
|
// tslint:enable no-console
|
||||||
|
|
||||||
|
this.extenders.forEach((extender) => {
|
||||||
|
extender({
|
||||||
|
section: this.name,
|
||||||
|
fields: options.fields,
|
||||||
|
level: options.level,
|
||||||
|
message: options.message as string,
|
||||||
|
type: options.type,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,16 +1,34 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const merge = require("webpack-merge");
|
const merge = require("webpack-merge");
|
||||||
|
|
||||||
module.exports = merge(require(path.join(__dirname, "../../scripts", "webpack.general.config.js"))(), {
|
module.exports = [
|
||||||
devtool: "none",
|
merge(require(path.join(__dirname, "../../scripts", "webpack.general.config.js"))(), {
|
||||||
mode: "production",
|
devtool: "none",
|
||||||
target: "node",
|
mode: "production",
|
||||||
output: {
|
target: "node",
|
||||||
path: path.join(__dirname, "out"),
|
output: {
|
||||||
filename: "main.js",
|
path: path.join(__dirname, "out"),
|
||||||
libraryTarget: "commonjs",
|
filename: "main.js",
|
||||||
},
|
libraryTarget: "commonjs",
|
||||||
entry: [
|
},
|
||||||
"./packages/logger/src/index.ts"
|
entry: [
|
||||||
],
|
"./packages/logger/src/index.ts"
|
||||||
});
|
],
|
||||||
|
}),
|
||||||
|
merge(require(path.join(__dirname, "../../scripts", "webpack.general.config.js"))(), {
|
||||||
|
devtool: "none",
|
||||||
|
mode: "production",
|
||||||
|
target: "node",
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, "out"),
|
||||||
|
filename: "extender.js",
|
||||||
|
libraryTarget: "commonjs",
|
||||||
|
},
|
||||||
|
externals: {
|
||||||
|
"@google-cloud/logging": "commonjs @google-cloud/logging",
|
||||||
|
},
|
||||||
|
entry: [
|
||||||
|
"./packages/logger/src/extender.ts"
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"xmlhttprequest": "1.8.0"
|
"xmlhttprequest": "1.8.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
"globals": {
|
||||||
|
"ts-jest": {
|
||||||
|
"diagnostics": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
"ts",
|
"ts",
|
||||||
"tsx",
|
"tsx",
|
||||||
@@ -26,7 +31,9 @@
|
|||||||
"@coder/ide/src/fill/evaluation": "<rootDir>/ide/src/fill/evaluation",
|
"@coder/ide/src/fill/evaluation": "<rootDir>/ide/src/fill/evaluation",
|
||||||
"@coder/ide/src/fill/client": "<rootDir>/ide/src/fill/client",
|
"@coder/ide/src/fill/client": "<rootDir>/ide/src/fill/client",
|
||||||
"@coder/(.*)/test": "<rootDir>/$1/test",
|
"@coder/(.*)/test": "<rootDir>/$1/test",
|
||||||
"@coder/(.*)": "<rootDir>/$1/src"
|
"@coder/(.*)": "<rootDir>/$1/src",
|
||||||
|
"vs/(.*)": "<rootDir>/../lib/vscode/src/vs/$1",
|
||||||
|
"vszip": "<rootDir>/../lib/vscode/src/vs/base/node/zip.ts"
|
||||||
},
|
},
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.tsx?$": "ts-jest"
|
"^.+\\.tsx?$": "ts-jest"
|
||||||
|
|||||||
47
packages/protocol/README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Protocol
|
||||||
|
|
||||||
|
This module provides a way for the browser to run Node modules like `fs`, `net`,
|
||||||
|
etc.
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
|
||||||
|
### Server-side proxies
|
||||||
|
The server-side proxies are regular classes that call native Node functions. The
|
||||||
|
only thing special about them is that they must return promises and they must
|
||||||
|
return serializable values.
|
||||||
|
|
||||||
|
The only exception to the promise rule are event-related methods such as
|
||||||
|
`onEvent` and `onDone` (these are synchronous). The server will simply
|
||||||
|
immediately bind and push all events it can to the client. It doesn't wait for
|
||||||
|
the client to start listening. This prevents issues with the server not
|
||||||
|
receiving the client's request to start listening in time.
|
||||||
|
|
||||||
|
However, there is a way to specify events that should not bind immediately and
|
||||||
|
should wait for the client to request it, because some events (like `data` on a
|
||||||
|
stream) cannot be bound immediately (because doing so changes how the stream
|
||||||
|
behaves).
|
||||||
|
|
||||||
|
### Client-side proxies
|
||||||
|
Client-side proxies are `Proxy` instances. They simply make remote calls for any
|
||||||
|
method you call on it. The only exception is for events. Each client proxy has a
|
||||||
|
local emitter which it uses in place of a remote call (this allows the call to
|
||||||
|
be completed synchronously on the client). Then when an event is received from
|
||||||
|
the server, it gets emitted on that local emitter.
|
||||||
|
|
||||||
|
When an event is listened to, the proxy also notifies the server so it can start
|
||||||
|
listening in case it isn't already (see the `data` example above). This only
|
||||||
|
works for events that only fire after they are bound.
|
||||||
|
|
||||||
|
### Client-side fills
|
||||||
|
The client-side fills implement the Node API and make calls to the server-side
|
||||||
|
proxies using the client-side proxies.
|
||||||
|
|
||||||
|
When a proxy returns a proxy (for example `fs.createWriteStream`), that proxy is
|
||||||
|
a promise (since communicating with the server is asynchronous). We have to
|
||||||
|
return the fill from `fs.createWriteStream` synchronously, so that means the
|
||||||
|
fill has to contain a proxy promise. To eliminate the need for calling `then`
|
||||||
|
and to keep the code looking clean every time you use the proxy, the proxy is
|
||||||
|
itself wrapped in another proxy which just calls the method after a `then`. This
|
||||||
|
works since all the methods return promises (aside from the event methods, but
|
||||||
|
those are not used by the fills directly—they are only used internally to
|
||||||
|
forward events to the fill if it is an event emitter).
|
||||||
@@ -4,14 +4,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"google-protobuf": "^3.6.1",
|
"google-protobuf": "^3.6.1",
|
||||||
"node-pty-prebuilt": "^0.7.6",
|
"trash": "^4.3.0",
|
||||||
"spdlog": "^0.7.2",
|
|
||||||
"tslib": "^1.9.3",
|
|
||||||
"ws": "^6.1.2"
|
"ws": "^6.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/google-protobuf": "^3.2.7",
|
"@types/google-protobuf": "^3.2.7",
|
||||||
|
"@types/rimraf": "^2.0.2",
|
||||||
"@types/text-encoding": "^0.0.35",
|
"@types/text-encoding": "^0.0.35",
|
||||||
|
"rimraf": "^2.6.3",
|
||||||
"text-encoding": "^0.7.0",
|
"text-encoding": "^0.7.0",
|
||||||
"ts-protoc-gen": "^0.8.0"
|
"ts-protoc-gen": "^0.8.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
import { EventEmitter } from "events";
|
import { PathLike } from "fs";
|
||||||
|
import { ExecException, ExecOptions } from "child_process";
|
||||||
|
import { promisify } from "util";
|
||||||
import { Emitter } from "@coder/events";
|
import { Emitter } from "@coder/events";
|
||||||
import { logger, field } from "@coder/logger";
|
import { logger, field } from "@coder/logger";
|
||||||
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, ClientMessage, WorkingInitMessage, EvalEventMessage } from "../proto";
|
import { ReadWriteConnection, InitData, SharedProcessData } from "../common/connection";
|
||||||
import { ReadWriteConnection, InitData, OperatingSystem, SharedProcessData } from "../common/connection";
|
import { ClientServerProxy, Module, ServerProxy } from "../common/proxy";
|
||||||
import { ActiveEvalHelper, EvalHelper, Disposer, ServerActiveEvalHelper } from "../common/helpers";
|
import { argumentToProto, protoToArgument, moduleToProto, protoToModule, protoToOperatingSystem } from "../common/util";
|
||||||
import { stringify, parse } from "../common/util";
|
import { Argument, Ping, ServerMessage, ClientMessage, Method, Event, Callback } from "../proto";
|
||||||
|
import { FsModule, ChildProcessModule, NetModule, NodePtyModule, SpdlogModule, TrashModule } from "./modules";
|
||||||
|
|
||||||
|
// tslint:disable no-any
|
||||||
|
|
||||||
|
interface ProxyData {
|
||||||
|
promise: Promise<void>;
|
||||||
|
instance: any;
|
||||||
|
callbacks: Map<number, (...args: any[]) => void>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client accepts an arbitrary connection intended to communicate with the Server.
|
* Client accepts a connection to communicate with the server.
|
||||||
*/
|
*/
|
||||||
export class Client {
|
export class Client {
|
||||||
private evalId = 0;
|
private messageId = 0;
|
||||||
private readonly evalDoneEmitter = new Emitter<EvalDoneMessage>();
|
private callbackId = 0;
|
||||||
private readonly evalFailedEmitter = new Emitter<EvalFailedMessage>();
|
private readonly proxies = new Map<number | Module, ProxyData>();
|
||||||
private readonly evalEventEmitter = new Emitter<EvalEventMessage>();
|
private readonly successEmitter = new Emitter<Method.Success>();
|
||||||
|
private readonly failEmitter = new Emitter<Method.Fail>();
|
||||||
|
private readonly eventEmitter = new Emitter<{ event: string; args: any[]; }>();
|
||||||
|
|
||||||
private _initData: InitData | undefined;
|
private _initData: InitData | undefined;
|
||||||
private readonly initDataEmitter = new Emitter<InitData>();
|
private readonly initDataEmitter = new Emitter<InitData>();
|
||||||
@@ -22,32 +35,133 @@ export class Client {
|
|||||||
private readonly sharedProcessActiveEmitter = new Emitter<SharedProcessData>();
|
private readonly sharedProcessActiveEmitter = new Emitter<SharedProcessData>();
|
||||||
public readonly onSharedProcessActive = this.sharedProcessActiveEmitter.event;
|
public readonly onSharedProcessActive = this.sharedProcessActiveEmitter.event;
|
||||||
|
|
||||||
|
private disconnected: boolean = false;
|
||||||
|
|
||||||
|
// The socket timeout is 60s, so we need to send a ping periodically to
|
||||||
|
// prevent it from closing.
|
||||||
|
private pingTimeout: NodeJS.Timer | number | undefined;
|
||||||
|
private readonly pingTimeoutDelay = 30000;
|
||||||
|
|
||||||
|
private readonly responseTimeout = 10000;
|
||||||
|
|
||||||
|
public readonly modules: {
|
||||||
|
[Module.ChildProcess]: ChildProcessModule,
|
||||||
|
[Module.Fs]: FsModule,
|
||||||
|
[Module.Net]: NetModule,
|
||||||
|
[Module.NodePty]: NodePtyModule,
|
||||||
|
[Module.Spdlog]: SpdlogModule,
|
||||||
|
[Module.Trash]: TrashModule,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param connection Established connection to the server
|
* @param connection Established connection to the server
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(private readonly connection: ReadWriteConnection) {
|
||||||
private readonly connection: ReadWriteConnection,
|
connection.onMessage(async (data) => {
|
||||||
) {
|
|
||||||
connection.onMessage((data) => {
|
|
||||||
let message: ServerMessage | undefined;
|
let message: ServerMessage | undefined;
|
||||||
try {
|
try {
|
||||||
message = ServerMessage.deserializeBinary(data);
|
message = ServerMessage.deserializeBinary(data);
|
||||||
this.handleMessage(message);
|
await this.handleMessage(message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to handle server message",
|
"Failed to handle server message",
|
||||||
field("id", message && message.hasEvalEvent() ? message.getEvalEvent()!.getId() : undefined),
|
field("id", message && this.getMessageId(message)),
|
||||||
field("length", data.byteLength),
|
field("length", data.byteLength),
|
||||||
field("error", error.message),
|
field("error", error.message),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.createProxy(Module.ChildProcess);
|
||||||
|
this.createProxy(Module.Fs);
|
||||||
|
this.createProxy(Module.Net);
|
||||||
|
this.createProxy(Module.NodePty);
|
||||||
|
this.createProxy(Module.Spdlog);
|
||||||
|
this.createProxy(Module.Trash);
|
||||||
|
|
||||||
|
this.modules = {
|
||||||
|
[Module.ChildProcess]: new ChildProcessModule(this.getProxy(Module.ChildProcess).instance),
|
||||||
|
[Module.Fs]: new FsModule(this.getProxy(Module.Fs).instance),
|
||||||
|
[Module.Net]: new NetModule(this.getProxy(Module.Net).instance),
|
||||||
|
[Module.NodePty]: new NodePtyModule(this.getProxy(Module.NodePty).instance),
|
||||||
|
[Module.Spdlog]: new SpdlogModule(this.getProxy(Module.Spdlog).instance),
|
||||||
|
[Module.Trash]: new TrashModule(this.getProxy(Module.Trash).instance),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Methods that don't follow the standard callback pattern (an error
|
||||||
|
// followed by a single result) need to provide a custom promisify function.
|
||||||
|
Object.defineProperty(this.modules[Module.Fs].exists, promisify.custom, {
|
||||||
|
value: (path: PathLike): Promise<boolean> => {
|
||||||
|
return new Promise((resolve): void => this.modules[Module.Fs].exists(path, resolve));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(this.modules[Module.ChildProcess].exec, promisify.custom, {
|
||||||
|
value: (
|
||||||
|
command: string,
|
||||||
|
options?: { encoding?: string | null } & ExecOptions | null,
|
||||||
|
): Promise<{ stdout: string | Buffer, stderr: string | Buffer }> => {
|
||||||
|
return new Promise((resolve, reject): void => {
|
||||||
|
this.modules[Module.ChildProcess].exec(command, options, (error: ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve({ stdout, stderr });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the connection is interrupted, the calls will neither succeed nor fail
|
||||||
|
* nor exit so we need to send a failure on all of them as well as trigger
|
||||||
|
* events so things like child processes can clean up and possibly restart.
|
||||||
|
*/
|
||||||
|
const handleDisconnect = (): void => {
|
||||||
|
this.disconnected = true;
|
||||||
|
logger.trace(() => [
|
||||||
|
"disconnected from server",
|
||||||
|
field("proxies", this.proxies.size),
|
||||||
|
field("callbacks", Array.from(this.proxies.values()).reduce((count, p) => count + p.callbacks.size, 0)),
|
||||||
|
field("success listeners", this.successEmitter.counts),
|
||||||
|
field("fail listeners", this.failEmitter.counts),
|
||||||
|
field("event listeners", this.eventEmitter.counts),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const message = new Method.Fail();
|
||||||
|
const error = new Error("disconnected");
|
||||||
|
message.setResponse(argumentToProto(error));
|
||||||
|
this.failEmitter.emit(message);
|
||||||
|
|
||||||
|
this.eventEmitter.emit({ event: "disconnected", args: [error] });
|
||||||
|
this.eventEmitter.emit({ event: "done", args: [] });
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onDown(() => handleDisconnect());
|
||||||
|
connection.onClose(() => {
|
||||||
|
clearTimeout(this.pingTimeout as any);
|
||||||
|
this.pingTimeout = undefined;
|
||||||
|
handleDisconnect();
|
||||||
|
this.proxies.clear();
|
||||||
|
this.successEmitter.dispose();
|
||||||
|
this.failEmitter.dispose();
|
||||||
|
this.eventEmitter.dispose();
|
||||||
|
this.initDataEmitter.dispose();
|
||||||
|
this.sharedProcessActiveEmitter.dispose();
|
||||||
|
});
|
||||||
|
connection.onUp(() => this.disconnected = false);
|
||||||
|
|
||||||
this.initDataPromise = new Promise((resolve): void => {
|
this.initDataPromise = new Promise((resolve): void => {
|
||||||
this.initDataEmitter.event(resolve);
|
this.initDataEmitter.event(resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.startPinging();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the connection.
|
||||||
|
*/
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.connection.close();
|
this.connection.close();
|
||||||
}
|
}
|
||||||
@@ -56,164 +170,375 @@ export class Client {
|
|||||||
return this.initDataPromise;
|
return this.initDataPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public run(func: (helper: ServerActiveEvalHelper) => Disposer): ActiveEvalHelper;
|
|
||||||
public run<T1>(func: (helper: ServerActiveEvalHelper, a1: T1) => Disposer, a1: T1): ActiveEvalHelper;
|
|
||||||
public run<T1, T2>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2) => Disposer, a1: T1, a2: T2): ActiveEvalHelper;
|
|
||||||
public run<T1, T2, T3>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2, a3: T3) => Disposer, a1: T1, a2: T2, a3: T3): ActiveEvalHelper;
|
|
||||||
public run<T1, T2, T3, T4>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2, a3: T3, a4: T4) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4): ActiveEvalHelper;
|
|
||||||
public run<T1, T2, T3, T4, T5>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): ActiveEvalHelper;
|
|
||||||
public run<T1, T2, T3, T4, T5, T6>(func: (helper: ServerActiveEvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6) => Disposer, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): ActiveEvalHelper;
|
|
||||||
/**
|
/**
|
||||||
* Run a function on the server and provide an event emitter which allows
|
* Make a remote call for a proxy's method using proto.
|
||||||
* listening and emitting to the emitter provided to that function. The
|
|
||||||
* function should return a disposer for cleaning up when the client
|
|
||||||
* disconnects and for notifying when disposal has happened outside manual
|
|
||||||
* activation.
|
|
||||||
*/
|
*/
|
||||||
public run<T1, T2, T3, T4, T5, T6>(func: (helper: ServerActiveEvalHelper, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6) => Disposer, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6): ActiveEvalHelper {
|
private remoteCall(proxyId: number | Module, method: string, args: any[]): Promise<any> {
|
||||||
const doEval = this.doEvaluate(func, a1, a2, a3, a4, a5, a6, true);
|
if (typeof proxyId === "number" && (this.disconnected || !this.proxies.has(proxyId))) {
|
||||||
|
// Can assume killing or closing works because a disconnected proxy is
|
||||||
// This takes server events and emits them to the client's emitter.
|
// disposed on the server's side, and a non-existent proxy has already
|
||||||
const eventEmitter = new EventEmitter();
|
// been disposed.
|
||||||
const d1 = this.evalEventEmitter.event((msg) => {
|
switch (method) {
|
||||||
if (msg.getId() === doEval.id) {
|
case "close":
|
||||||
eventEmitter.emit(msg.getEvent(), ...msg.getArgsList().map(parse));
|
case "kill":
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
doEval.completed.then(() => {
|
return Promise.reject(
|
||||||
d1.dispose();
|
new Error(`Unable to call "${method}" on proxy ${proxyId}: disconnected`),
|
||||||
}).catch((ex) => {
|
);
|
||||||
d1.dispose();
|
}
|
||||||
// This error event is only received by the client.
|
|
||||||
eventEmitter.emit("error", ex);
|
|
||||||
});
|
|
||||||
|
|
||||||
return new ActiveEvalHelper({
|
const message = new Method();
|
||||||
// This takes client events and emits them to the server's emitter and
|
const id = this.messageId++;
|
||||||
// listens to events received from the server (via the event hook above).
|
let proxyMessage: Method.Named | Method.Numbered;
|
||||||
// tslint:disable no-any
|
if (typeof proxyId === "string") {
|
||||||
on: (event: string, cb: (...args: any[]) => void): EventEmitter => eventEmitter.on(event, cb),
|
proxyMessage = new Method.Named();
|
||||||
emit: (event: string, ...args: any[]): void => {
|
proxyMessage.setModule(moduleToProto(proxyId));
|
||||||
const eventsMsg = new EvalEventMessage();
|
message.setNamedProxy(proxyMessage);
|
||||||
eventsMsg.setId(doEval.id);
|
} else {
|
||||||
eventsMsg.setEvent(event);
|
proxyMessage = new Method.Numbered();
|
||||||
eventsMsg.setArgsList(args.map((a) => stringify(a)));
|
proxyMessage.setProxyId(proxyId);
|
||||||
const clientMsg = new ClientMessage();
|
message.setNumberedProxy(proxyMessage);
|
||||||
clientMsg.setEvalEvent(eventsMsg);
|
}
|
||||||
this.connection.send(clientMsg.serializeBinary());
|
proxyMessage.setId(id);
|
||||||
},
|
proxyMessage.setMethod(method);
|
||||||
removeAllListeners: (event: string): EventEmitter => eventEmitter.removeAllListeners(event),
|
|
||||||
// tslint:enable no-any
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public evaluate<R>(func: (helper: EvalHelper) => R | Promise<R>): Promise<R>;
|
const storeCallback = (cb: (...args: any[]) => void): number => {
|
||||||
public evaluate<R, T1>(func: (helper: EvalHelper, a1: T1) => R | Promise<R>, a1: T1): Promise<R>;
|
const callbackId = this.callbackId++;
|
||||||
public evaluate<R, T1, T2>(func: (helper: EvalHelper, a1: T1, a2: T2) => R | Promise<R>, a1: T1, a2: T2): Promise<R>;
|
logger.trace(() => [
|
||||||
public evaluate<R, T1, T2, T3>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3) => R | Promise<R>, a1: T1, a2: T2, a3: T3): Promise<R>;
|
"storing callback",
|
||||||
public evaluate<R, T1, T2, T3, T4>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4): Promise<R>;
|
field("proxyId", proxyId),
|
||||||
public evaluate<R, T1, T2, T3, T4, T5>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): Promise<R>;
|
field("callbackId", callbackId),
|
||||||
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (helper: EvalHelper, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): Promise<R>;
|
]);
|
||||||
/**
|
|
||||||
* Evaluates a function on the server.
|
|
||||||
* To pass variables, ensure they are serializable and passed through the included function.
|
|
||||||
* @example
|
|
||||||
* const returned = await this.client.evaluate((helper, value) => {
|
|
||||||
* return value;
|
|
||||||
* }, "hi");
|
|
||||||
* console.log(returned);
|
|
||||||
* // output: "hi"
|
|
||||||
* @param func Function to evaluate
|
|
||||||
* @returns Promise rejected or resolved from the evaluated function
|
|
||||||
*/
|
|
||||||
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (helper: EvalHelper, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6) => R | Promise<R>, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6): Promise<R> {
|
|
||||||
return this.doEvaluate(func, a1, a2, a3, a4, a5, a6, false).completed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
this.getProxy(proxyId).callbacks.set(callbackId, cb);
|
||||||
private doEvaluate<R, T1, T2, T3, T4, T5, T6>(func: (...args: any[]) => void | Promise<void> | R | Promise<R>, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6, active: boolean = false): {
|
|
||||||
readonly completed: Promise<R>;
|
|
||||||
readonly id: number;
|
|
||||||
} {
|
|
||||||
const newEval = new NewEvalMessage();
|
|
||||||
const id = this.evalId++;
|
|
||||||
newEval.setId(id);
|
|
||||||
newEval.setActive(active);
|
|
||||||
newEval.setArgsList([a1, a2, a3, a4, a5, a6].map((a) => stringify(a)));
|
|
||||||
newEval.setFunction(func.toString());
|
|
||||||
|
|
||||||
const clientMsg = new ClientMessage();
|
return callbackId;
|
||||||
clientMsg.setNewEval(newEval);
|
};
|
||||||
this.connection.send(clientMsg.serializeBinary());
|
|
||||||
|
|
||||||
const completed = new Promise<R>((resolve, reject): void => {
|
logger.trace(() => [
|
||||||
|
"sending",
|
||||||
|
field("id", id),
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
field("method", method),
|
||||||
|
]);
|
||||||
|
|
||||||
|
proxyMessage.setArgsList(args.map((a) => argumentToProto<ClientServerProxy>(
|
||||||
|
a,
|
||||||
|
storeCallback,
|
||||||
|
(p) => p.proxyId,
|
||||||
|
)));
|
||||||
|
|
||||||
|
const clientMessage = new ClientMessage();
|
||||||
|
clientMessage.setMethod(message);
|
||||||
|
this.connection.send(clientMessage.serializeBinary());
|
||||||
|
|
||||||
|
// The server will send back a fail or success message when the method
|
||||||
|
// has completed, so we listen for that based on the message's unique ID.
|
||||||
|
const promise = new Promise((resolve, reject): void => {
|
||||||
const dispose = (): void => {
|
const dispose = (): void => {
|
||||||
d1.dispose();
|
d1.dispose();
|
||||||
d2.dispose();
|
d2.dispose();
|
||||||
|
clearTimeout(timeout as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
const d1 = this.evalDoneEmitter.event((doneMsg) => {
|
const timeout = setTimeout(() => {
|
||||||
if (doneMsg.getId() === id) {
|
dispose();
|
||||||
dispose();
|
reject(new Error("timed out"));
|
||||||
resolve(parse(doneMsg.getResponse()));
|
}, this.responseTimeout);
|
||||||
}
|
|
||||||
|
const d1 = this.successEmitter.event(id, (message) => {
|
||||||
|
dispose();
|
||||||
|
resolve(this.protoToArgument(message.getResponse(), promise));
|
||||||
});
|
});
|
||||||
|
|
||||||
const d2 = this.evalFailedEmitter.event((failedMsg) => {
|
const d2 = this.failEmitter.event(id, (message) => {
|
||||||
if (failedMsg.getId() === id) {
|
dispose();
|
||||||
dispose();
|
reject(protoToArgument(message.getResponse()));
|
||||||
reject(parse(failedMsg.getResponse()));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return { completed, id };
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a message from the server. All incoming server messages should be
|
* Handle all messages from the server.
|
||||||
* routed through here.
|
|
||||||
*/
|
*/
|
||||||
private handleMessage(message: ServerMessage): void {
|
private async handleMessage(message: ServerMessage): Promise<void> {
|
||||||
if (message.hasInit()) {
|
switch (message.getMsgCase()) {
|
||||||
const init = message.getInit()!;
|
case ServerMessage.MsgCase.INIT:
|
||||||
let opSys: OperatingSystem;
|
const init = message.getInit()!;
|
||||||
switch (init.getOperatingSystem()) {
|
this._initData = {
|
||||||
case WorkingInitMessage.OperatingSystem.WINDOWS:
|
dataDirectory: init.getDataDirectory(),
|
||||||
opSys = OperatingSystem.Windows;
|
homeDirectory: init.getHomeDirectory(),
|
||||||
break;
|
tmpDirectory: init.getTmpDirectory(),
|
||||||
case WorkingInitMessage.OperatingSystem.LINUX:
|
workingDirectory: init.getWorkingDirectory(),
|
||||||
opSys = OperatingSystem.Linux;
|
os: protoToOperatingSystem(init.getOperatingSystem()),
|
||||||
break;
|
shell: init.getShell(),
|
||||||
case WorkingInitMessage.OperatingSystem.MAC:
|
extensionsDirectory: init.getExtensionsDirectory(),
|
||||||
opSys = OperatingSystem.Mac;
|
builtInExtensionsDirectory: init.getBuiltinExtensionsDir(),
|
||||||
break;
|
extraExtensionDirectories: init.getExtraExtensionDirectoriesList(),
|
||||||
default:
|
extraBuiltinExtensionDirectories: init.getExtraBuiltinExtensionDirectoriesList(),
|
||||||
throw new Error(`unsupported operating system ${init.getOperatingSystem()}`);
|
};
|
||||||
}
|
this.initDataEmitter.emit(this._initData);
|
||||||
this._initData = {
|
break;
|
||||||
dataDirectory: init.getDataDirectory(),
|
case ServerMessage.MsgCase.SUCCESS:
|
||||||
homeDirectory: init.getHomeDirectory(),
|
this.emitSuccess(message.getSuccess()!);
|
||||||
tmpDirectory: init.getTmpDirectory(),
|
break;
|
||||||
workingDirectory: init.getWorkingDirectory(),
|
case ServerMessage.MsgCase.FAIL:
|
||||||
os: opSys,
|
this.emitFail(message.getFail()!);
|
||||||
shell: init.getShell(),
|
break;
|
||||||
builtInExtensionsDirectory: init.getBuiltinExtensionsDir(),
|
case ServerMessage.MsgCase.EVENT:
|
||||||
};
|
await this.emitEvent(message.getEvent()!);
|
||||||
this.initDataEmitter.emit(this._initData);
|
break;
|
||||||
} else if (message.hasEvalDone()) {
|
case ServerMessage.MsgCase.CALLBACK:
|
||||||
this.evalDoneEmitter.emit(message.getEvalDone()!);
|
await this.runCallback(message.getCallback()!);
|
||||||
} else if (message.hasEvalFailed()) {
|
break;
|
||||||
this.evalFailedEmitter.emit(message.getEvalFailed()!);
|
case ServerMessage.MsgCase.SHARED_PROCESS_ACTIVE:
|
||||||
} else if (message.hasEvalEvent()) {
|
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
|
||||||
this.evalEventEmitter.emit(message.getEvalEvent()!);
|
this.sharedProcessActiveEmitter.emit({
|
||||||
} else if (message.hasSharedProcessActive()) {
|
socketPath: sharedProcessActiveMessage.getSocketPath(),
|
||||||
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
|
logPath: sharedProcessActiveMessage.getLogPath(),
|
||||||
this.sharedProcessActiveEmitter.emit({
|
});
|
||||||
socketPath: sharedProcessActiveMessage.getSocketPath(),
|
break;
|
||||||
logPath: sharedProcessActiveMessage.getLogPath(),
|
case ServerMessage.MsgCase.PONG:
|
||||||
});
|
// Nothing to do since pings are on a timer rather than waiting for the
|
||||||
|
// next pong in case a message from either the client or server is dropped
|
||||||
|
// which would break the ping cycle.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("unknown message type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert message to a success event.
|
||||||
|
*/
|
||||||
|
private emitSuccess(message: Method.Success): void {
|
||||||
|
logger.trace(() => [
|
||||||
|
"received resolve",
|
||||||
|
field("id", message.getId()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.successEmitter.emit(message.getId(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert message to a fail event.
|
||||||
|
*/
|
||||||
|
private emitFail(message: Method.Fail): void {
|
||||||
|
logger.trace(() => [
|
||||||
|
"received reject",
|
||||||
|
field("id", message.getId()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.failEmitter.emit(message.getId(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event received from the server. We could send requests for "on" to
|
||||||
|
* the server and serialize functions using IDs, but doing it that way makes
|
||||||
|
* it possible to miss events depending on whether the server receives the
|
||||||
|
* request before it emits. Instead, emit all events from the server so all
|
||||||
|
* events are always caught on the client.
|
||||||
|
*/
|
||||||
|
private async emitEvent(message: Event): Promise<void> {
|
||||||
|
const eventMessage = message.getNamedEvent()! || message.getNumberedEvent()!;
|
||||||
|
const proxyId = message.getNamedEvent()
|
||||||
|
? protoToModule(message.getNamedEvent()!.getModule())
|
||||||
|
: message.getNumberedEvent()!.getProxyId();
|
||||||
|
const event = eventMessage.getEvent();
|
||||||
|
await this.ensureResolved(proxyId);
|
||||||
|
logger.trace(() => [
|
||||||
|
"received event",
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
field("event", event),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const args = eventMessage.getArgsList().map((a) => this.protoToArgument(a));
|
||||||
|
this.eventEmitter.emit(proxyId, { event, args });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a callback as requested by the server. Since we don't know when
|
||||||
|
* callbacks get garbage collected we dispose them only when the proxy
|
||||||
|
* disposes. That means they should only be used if they run for the lifetime
|
||||||
|
* of the proxy (like child_process.exec), otherwise we'll leak. They should
|
||||||
|
* also only be used when passed together with the method. If they are sent
|
||||||
|
* afterward, they may never be called due to timing issues.
|
||||||
|
*/
|
||||||
|
private async runCallback(message: Callback): Promise<void> {
|
||||||
|
const callbackMessage = message.getNamedCallback()! || message.getNumberedCallback()!;
|
||||||
|
const proxyId = message.getNamedCallback()
|
||||||
|
? protoToModule(message.getNamedCallback()!.getModule())
|
||||||
|
: message.getNumberedCallback()!.getProxyId();
|
||||||
|
const callbackId = callbackMessage.getCallbackId();
|
||||||
|
await this.ensureResolved(proxyId);
|
||||||
|
logger.trace(() => [
|
||||||
|
"running callback",
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
field("callbackId", callbackId),
|
||||||
|
]);
|
||||||
|
const args = callbackMessage.getArgsList().map((a) => this.protoToArgument(a));
|
||||||
|
this.getProxy(proxyId).callbacks.get(callbackId)!(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the ping loop. Does nothing if already pinging.
|
||||||
|
*/
|
||||||
|
private readonly startPinging = (): void => {
|
||||||
|
if (typeof this.pingTimeout !== "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schedulePing = (): void => {
|
||||||
|
this.pingTimeout = setTimeout(() => {
|
||||||
|
const clientMsg = new ClientMessage();
|
||||||
|
clientMsg.setPing(new Ping());
|
||||||
|
this.connection.send(clientMsg.serializeBinary());
|
||||||
|
schedulePing();
|
||||||
|
}, this.pingTimeoutDelay);
|
||||||
|
};
|
||||||
|
|
||||||
|
schedulePing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the message's ID if it has one or a string identifier. For logging
|
||||||
|
* errors with an ID to make the error more useful.
|
||||||
|
*/
|
||||||
|
private getMessageId(message: ServerMessage): number | string | undefined {
|
||||||
|
if (message.hasInit()) {
|
||||||
|
return "init";
|
||||||
|
} else if (message.hasSuccess()) {
|
||||||
|
return message.getSuccess()!.getId();
|
||||||
|
} else if (message.hasFail()) {
|
||||||
|
return message.getFail()!.getId();
|
||||||
|
} else if (message.hasEvent()) {
|
||||||
|
const eventMessage = message.getEvent()!.getNamedEvent()!
|
||||||
|
|| message.getEvent()!.getNumberedEvent()!;
|
||||||
|
|
||||||
|
return `event: ${eventMessage.getEvent()}`;
|
||||||
|
} else if (message.hasCallback()) {
|
||||||
|
const callbackMessage = message.getCallback()!.getNamedCallback()!
|
||||||
|
|| message.getCallback()!.getNumberedCallback()!;
|
||||||
|
|
||||||
|
return `callback: ${callbackMessage.getCallbackId()}`;
|
||||||
|
} else if (message.hasSharedProcessActive()) {
|
||||||
|
return "shared";
|
||||||
|
} else if (message.hasPong()) {
|
||||||
|
return "pong";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a proxy that makes remote calls.
|
||||||
|
*/
|
||||||
|
private createProxy<T extends ClientServerProxy>(proxyId: number | Module, promise: Promise<any> = Promise.resolve()): T {
|
||||||
|
logger.trace(() => [
|
||||||
|
"creating proxy",
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const instance = new Proxy({
|
||||||
|
proxyId,
|
||||||
|
onDone: (cb: (...args: any[]) => void): void => {
|
||||||
|
this.eventEmitter.event(proxyId, (event) => {
|
||||||
|
if (event.event === "done") {
|
||||||
|
cb(...event.args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEvent: (cb: (event: string, ...args: any[]) => void): void => {
|
||||||
|
this.eventEmitter.event(proxyId, (event) => {
|
||||||
|
cb(event.event, ...event.args);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as ClientServerProxy, {
|
||||||
|
get: (target: any, name: string): any => {
|
||||||
|
// When resolving a promise with a proxy, it will check for "then".
|
||||||
|
if (name === "then") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof target[name] === "undefined") {
|
||||||
|
target[name] = (...args: any[]): Promise<any> | ServerProxy => {
|
||||||
|
return this.remoteCall(proxyId, name, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return target[name];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.proxies.set(proxyId, {
|
||||||
|
promise,
|
||||||
|
instance,
|
||||||
|
callbacks: new Map(),
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.onDone(() => {
|
||||||
|
const log = (): void => {
|
||||||
|
logger.trace(() => [
|
||||||
|
typeof proxyId === "number" ? "disposed proxy" : "disposed proxy callbacks",
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
field("disconnected", this.disconnected),
|
||||||
|
field("callbacks", Array.from(this.proxies.values()).reduce((count, proxy) => count + proxy.callbacks.size, 0)),
|
||||||
|
field("success listeners", this.successEmitter.counts),
|
||||||
|
field("fail listeners", this.failEmitter.counts),
|
||||||
|
field("event listeners", this.eventEmitter.counts),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Uniquely identified items (top-level module proxies) can continue to
|
||||||
|
// be used so we don't need to delete them.
|
||||||
|
if (typeof proxyId === "number") {
|
||||||
|
const dispose = (): void => {
|
||||||
|
this.proxies.delete(proxyId);
|
||||||
|
this.eventEmitter.dispose(proxyId);
|
||||||
|
log();
|
||||||
|
};
|
||||||
|
if (!this.disconnected) {
|
||||||
|
instance.dispose().then(dispose).catch(dispose);
|
||||||
|
} else {
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The callbacks will still be unusable though.
|
||||||
|
this.getProxy(proxyId).callbacks.clear();
|
||||||
|
log();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We aren't guaranteed the promise will call all the `then` callbacks
|
||||||
|
* synchronously once it resolves, so the event message can come in and fire
|
||||||
|
* before a caller has been able to attach an event. Waiting for the promise
|
||||||
|
* ensures it runs after everything else.
|
||||||
|
*/
|
||||||
|
private async ensureResolved(proxyId: number | Module): Promise<void> {
|
||||||
|
await this.getProxy(proxyId).promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as protoToArgument except provides createProxy.
|
||||||
|
*/
|
||||||
|
private protoToArgument(value?: Argument, promise?: Promise<any>): any {
|
||||||
|
return protoToArgument(value, undefined, (id) => this.createProxy(id, promise));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a proxy. Error if it doesn't exist.
|
||||||
|
*/
|
||||||
|
private getProxy(proxyId: number | Module): ProxyData {
|
||||||
|
if (!this.proxies.has(proxyId)) {
|
||||||
|
throw new Error(`proxy ${proxyId} disposed too early`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.proxies.get(proxyId)!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
151
packages/protocol/src/browser/modules/child_process.ts
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import * as cp from "child_process";
|
||||||
|
import * as net from "net";
|
||||||
|
import * as stream from "stream";
|
||||||
|
import { callbackify } from "util";
|
||||||
|
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
|
||||||
|
import { ChildProcessModuleProxy, ChildProcessProxy } from "../../node/modules/child_process";
|
||||||
|
import { ClientWritableProxy, ClientReadableProxy, Readable, Writable } from "./stream";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
export interface ClientChildProcessProxy extends ChildProcessProxy, ClientServerProxy<cp.ChildProcess> {}
|
||||||
|
|
||||||
|
export interface ClientChildProcessProxies {
|
||||||
|
childProcess: ClientChildProcessProxy;
|
||||||
|
stdin?: ClientWritableProxy | null;
|
||||||
|
stdout?: ClientReadableProxy | null;
|
||||||
|
stderr?: ClientReadableProxy | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChildProcess extends ClientProxy<ClientChildProcessProxy> implements cp.ChildProcess {
|
||||||
|
public readonly stdin: stream.Writable;
|
||||||
|
public readonly stdout: stream.Readable;
|
||||||
|
public readonly stderr: stream.Readable;
|
||||||
|
public readonly stdio: [stream.Writable, stream.Readable, stream.Readable];
|
||||||
|
|
||||||
|
private _connected: boolean = false;
|
||||||
|
private _killed: boolean = false;
|
||||||
|
private _pid = -1;
|
||||||
|
|
||||||
|
public constructor(proxyPromises: Promise<ClientChildProcessProxies>) {
|
||||||
|
super(proxyPromises.then((p) => p.childProcess));
|
||||||
|
this.stdin = new Writable(proxyPromises.then((p) => p.stdin!));
|
||||||
|
this.stdout = new Readable(proxyPromises.then((p) => p.stdout!));
|
||||||
|
this.stderr = new Readable(proxyPromises.then((p) => p.stderr!));
|
||||||
|
this.stdio = [this.stdin, this.stdout, this.stderr];
|
||||||
|
|
||||||
|
this.catch(this.proxy.getPid().then((pid) => {
|
||||||
|
this._pid = pid;
|
||||||
|
this._connected = true;
|
||||||
|
}));
|
||||||
|
this.on("disconnect", () => this._connected = false);
|
||||||
|
this.on("exit", () => {
|
||||||
|
this._connected = false;
|
||||||
|
this._killed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get pid(): number {
|
||||||
|
return this._pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connected(): boolean {
|
||||||
|
return this._connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get killed(): boolean {
|
||||||
|
return this._killed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public kill(): void {
|
||||||
|
this._killed = true;
|
||||||
|
this.catch(this.proxy.kill());
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnect(): void {
|
||||||
|
this.catch(this.proxy.disconnect());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref(): void {
|
||||||
|
this.catch(this.proxy.ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
public unref(): void {
|
||||||
|
this.catch(this.proxy.unref());
|
||||||
|
}
|
||||||
|
|
||||||
|
public send(
|
||||||
|
message: any, // tslint:disable-line no-any
|
||||||
|
sendHandle?: net.Socket | net.Server | ((error: Error) => void),
|
||||||
|
options?: cp.MessageOptions | ((error: Error) => void),
|
||||||
|
callback?: (error: Error) => void): boolean {
|
||||||
|
if (typeof sendHandle === "function") {
|
||||||
|
callback = sendHandle;
|
||||||
|
sendHandle = undefined;
|
||||||
|
} else if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
if (sendHandle || options) {
|
||||||
|
throw new Error("sendHandle and options are not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackify(this.proxy.send)(message, (error) => {
|
||||||
|
if (callback) {
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true; // Always true since we can't get this synchronously.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exit and close the process when disconnected.
|
||||||
|
*/
|
||||||
|
protected handleDisconnect(): void {
|
||||||
|
this.emit("exit", 1);
|
||||||
|
this.emit("close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientChildProcessModuleProxy extends ChildProcessModuleProxy, ClientServerProxy {
|
||||||
|
exec(command: string, options?: { encoding?: string | null } & cp.ExecOptions | null, callback?: ((error: cp.ExecException | null, stdin: string | Buffer, stdout: string | Buffer) => void)): Promise<ClientChildProcessProxies>;
|
||||||
|
fork(modulePath: string, args?: string[], options?: cp.ForkOptions): Promise<ClientChildProcessProxies>;
|
||||||
|
spawn(command: string, args?: string[], options?: cp.SpawnOptions): Promise<ClientChildProcessProxies>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChildProcessModule {
|
||||||
|
public constructor(private readonly proxy: ClientChildProcessModuleProxy) {}
|
||||||
|
|
||||||
|
public exec = (
|
||||||
|
command: string,
|
||||||
|
options?: { encoding?: string | null } & cp.ExecOptions | null
|
||||||
|
| ((error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void),
|
||||||
|
callback?: ((error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void),
|
||||||
|
): cp.ChildProcess => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChildProcess(this.proxy.exec(command, options, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
public fork = (modulePath: string, args?: string[] | cp.ForkOptions, options?: cp.ForkOptions): cp.ChildProcess => {
|
||||||
|
if (!Array.isArray(args)) {
|
||||||
|
options = args;
|
||||||
|
args = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChildProcess(this.proxy.fork(modulePath, args, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public spawn = (command: string, args?: string[] | cp.SpawnOptions, options?: cp.SpawnOptions): cp.ChildProcess => {
|
||||||
|
if (!Array.isArray(args)) {
|
||||||
|
options = args;
|
||||||
|
args = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChildProcess(this.proxy.spawn(command, args, options));
|
||||||
|
}
|
||||||
|
}
|
||||||
380
packages/protocol/src/browser/modules/fs.ts
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { callbackify } from "util";
|
||||||
|
import { Batch, ClientProxy, ClientServerProxy } from "../../common/proxy";
|
||||||
|
import { IEncodingOptions, IEncodingOptionsCallback } from "../../common/util";
|
||||||
|
import { FsModuleProxy, ReadStreamProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "../../node/modules/fs";
|
||||||
|
import { Readable, Writable } from "./stream";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs no-any
|
||||||
|
|
||||||
|
class StatBatch extends Batch<IStats, { path: fs.PathLike }> {
|
||||||
|
public constructor(private readonly proxy: FsModuleProxy) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected remoteCall(batch: { path: fs.PathLike }[]): Promise<(IStats | Error)[]> {
|
||||||
|
return this.proxy.statBatch(batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LstatBatch extends Batch<IStats, { path: fs.PathLike }> {
|
||||||
|
public constructor(private readonly proxy: FsModuleProxy) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected remoteCall(batch: { path: fs.PathLike }[]): Promise<(IStats | Error)[]> {
|
||||||
|
return this.proxy.lstatBatch(batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReaddirBatch extends Batch<Buffer[] | fs.Dirent[] | string[], { path: fs.PathLike, options: IEncodingOptions }> {
|
||||||
|
public constructor(private readonly proxy: FsModuleProxy) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected remoteCall(queue: { path: fs.PathLike, options: IEncodingOptions }[]): Promise<(Buffer[] | fs.Dirent[] | string[] | Error)[]> {
|
||||||
|
return this.proxy.readdirBatch(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientWatcherProxy extends WatcherProxy, ClientServerProxy<fs.FSWatcher> {}
|
||||||
|
|
||||||
|
class Watcher extends ClientProxy<ClientWatcherProxy> implements fs.FSWatcher {
|
||||||
|
public close(): void {
|
||||||
|
this.catch(this.proxy.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleDisconnect(): void {
|
||||||
|
this.emit("close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientReadStreamProxy extends ReadStreamProxy, ClientServerProxy<fs.ReadStream> {}
|
||||||
|
|
||||||
|
class ReadStream extends Readable<ClientReadStreamProxy> implements fs.ReadStream {
|
||||||
|
public get bytesRead(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get path(): string | Buffer {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.catch(this.proxy.close());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientWriteStreamProxy extends WriteStreamProxy, ClientServerProxy<fs.WriteStream> {}
|
||||||
|
|
||||||
|
class WriteStream extends Writable<ClientWriteStreamProxy> implements fs.WriteStream {
|
||||||
|
public get bytesWritten(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get path(): string | Buffer {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.catch(this.proxy.close());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientFsModuleProxy extends FsModuleProxy, ClientServerProxy {
|
||||||
|
createReadStream(path: fs.PathLike, options?: any): Promise<ClientReadStreamProxy>;
|
||||||
|
createWriteStream(path: fs.PathLike, options?: any): Promise<ClientWriteStreamProxy>;
|
||||||
|
watch(filename: fs.PathLike, options?: IEncodingOptions): Promise<ClientWatcherProxy>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FsModule {
|
||||||
|
private readonly statBatch: StatBatch;
|
||||||
|
private readonly lstatBatch: LstatBatch;
|
||||||
|
private readonly readdirBatch: ReaddirBatch;
|
||||||
|
|
||||||
|
public constructor(private readonly proxy: ClientFsModuleProxy) {
|
||||||
|
this.statBatch = new StatBatch(this.proxy);
|
||||||
|
this.lstatBatch = new LstatBatch(this.proxy);
|
||||||
|
this.readdirBatch = new ReaddirBatch(this.proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public access = (path: fs.PathLike, mode: number | undefined | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
if (typeof mode === "function") {
|
||||||
|
callback = mode;
|
||||||
|
mode = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.access)(path, mode, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public appendFile = (path: fs.PathLike | number, data: any, options?: fs.WriteFileOptions | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.appendFile)(path, data, options, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public chmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.chmod)(path, mode, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public chown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.chown)(path, uid, gid, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public close = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.close)(fd, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public copyFile = (src: fs.PathLike, dest: fs.PathLike, flags: number | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
if (typeof flags === "function") {
|
||||||
|
callback = flags;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.copyFile)(
|
||||||
|
src, dest, typeof flags !== "function" ? flags : undefined, callback!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createReadStream = (path: fs.PathLike, options?: any): fs.ReadStream => {
|
||||||
|
return new ReadStream(this.proxy.createReadStream(path, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public createWriteStream = (path: fs.PathLike, options?: any): fs.WriteStream => {
|
||||||
|
return new WriteStream(this.proxy.createWriteStream(path, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public exists = (path: fs.PathLike, callback: (exists: boolean) => void): void => {
|
||||||
|
this.proxy.exists(path).then((exists) => callback(exists)).catch(() => callback(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public fchmod = (fd: number, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.fchmod)(fd, mode, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fchown = (fd: number, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.fchown)(fd, uid, gid, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fdatasync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.fdatasync)(fd, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fstat = (fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
||||||
|
callbackify(this.proxy.fstat)(fd, (error, stats) => {
|
||||||
|
callback(error, stats && new Stats(stats));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public fsync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.fsync)(fd, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ftruncate = (fd: number, len: number | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
if (typeof len === "function") {
|
||||||
|
callback = len;
|
||||||
|
len = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.ftruncate)(fd, len, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public futimes = (fd: number, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.futimes)(fd, atime, mtime, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public lchmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.lchmod)(path, mode, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public lchown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.lchown)(path, uid, gid, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public link = (existingPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.link)(existingPath, newPath, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public lstat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
||||||
|
callbackify(this.lstatBatch.add)({ path }, (error, stats) => {
|
||||||
|
callback(error, stats && new Stats(stats));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public mkdir = (path: fs.PathLike, mode: number | string | fs.MakeDirectoryOptions | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
if (typeof mode === "function") {
|
||||||
|
callback = mode;
|
||||||
|
mode = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.mkdir)(path, mode, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mkdtemp = (prefix: string, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, folder: string | Buffer) => void): void => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.mkdtemp)(prefix, options, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public open = (path: fs.PathLike, flags: string | number, mode: string | number | undefined | null | ((err: NodeJS.ErrnoException, fd: number) => void), callback?: (err: NodeJS.ErrnoException, fd: number) => void): void => {
|
||||||
|
if (typeof mode === "function") {
|
||||||
|
callback = mode;
|
||||||
|
mode = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.open)(path, flags, mode, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public read = (fd: number, buffer: Buffer, offset: number, length: number, position: number | null, callback: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void => {
|
||||||
|
this.proxy.read(fd, length, position).then((response) => {
|
||||||
|
buffer.set(response.buffer, offset);
|
||||||
|
callback(undefined!, response.bytesRead, response.buffer);
|
||||||
|
}).catch((error) => {
|
||||||
|
callback(error, undefined!, undefined!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public readFile = (path: fs.PathLike | number, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.readFile)(path, options, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readdir = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, files: Buffer[] | fs.Dirent[] | string[]) => void): void => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.readdirBatch.add)({ path, options }, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readlink = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, linkString: string | Buffer) => void): void => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.readlink)(path, options, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public realpath = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, resolvedPath: string | Buffer) => void): void => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.realpath)(path, options, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public rename = (oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.rename)(oldPath, newPath, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public rmdir = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.rmdir)(path, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public stat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
|
||||||
|
callbackify(this.statBatch.add)({ path }, (error, stats) => {
|
||||||
|
callback(error, stats && new Stats(stats));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public symlink = (target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
if (typeof type === "function") {
|
||||||
|
callback = type;
|
||||||
|
type = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.symlink)(target, path, type, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public truncate = (path: fs.PathLike, len: number | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
if (typeof len === "function") {
|
||||||
|
callback = len;
|
||||||
|
len = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.truncate)(path, len, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unlink = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.unlink)(path, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public utimes = (path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
callbackify(this.proxy.utimes)(path, atime, mtime, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public write = (fd: number, buffer: Buffer, offset: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), length: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), position: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void => {
|
||||||
|
if (typeof offset === "function") {
|
||||||
|
callback = offset;
|
||||||
|
offset = undefined;
|
||||||
|
}
|
||||||
|
if (typeof length === "function") {
|
||||||
|
callback = length;
|
||||||
|
length = undefined;
|
||||||
|
}
|
||||||
|
if (typeof position === "function") {
|
||||||
|
callback = position;
|
||||||
|
position = undefined;
|
||||||
|
}
|
||||||
|
this.proxy.write(fd, buffer, offset, length, position).then((r) => {
|
||||||
|
callback!(undefined!, r.bytesWritten, r.buffer);
|
||||||
|
}).catch((error) => {
|
||||||
|
callback!(error, undefined!, undefined!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeFile = (path: fs.PathLike | number, data: any, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException) => void): void => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.writeFile)(path, data, options, callback!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public watch = (filename: fs.PathLike, options?: IEncodingOptions | ((event: string, filename: string | Buffer) => void), listener?: ((event: string, filename: string | Buffer) => void)): fs.FSWatcher => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
listener = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const watcher = new Watcher(this.proxy.watch(filename, options));
|
||||||
|
if (listener) {
|
||||||
|
watcher.on("change", listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return watcher;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Stats implements fs.Stats {
|
||||||
|
public constructor(private readonly stats: IStats) {}
|
||||||
|
|
||||||
|
public get dev(): number { return this.stats.dev; }
|
||||||
|
public get ino(): number { return this.stats.ino; }
|
||||||
|
public get mode(): number { return this.stats.mode; }
|
||||||
|
public get nlink(): number { return this.stats.nlink; }
|
||||||
|
public get uid(): number { return this.stats.uid; }
|
||||||
|
public get gid(): number { return this.stats.gid; }
|
||||||
|
public get rdev(): number { return this.stats.rdev; }
|
||||||
|
public get size(): number { return this.stats.size; }
|
||||||
|
public get blksize(): number { return this.stats.blksize; }
|
||||||
|
public get blocks(): number { return this.stats.blocks; }
|
||||||
|
public get atime(): Date { return this.stats.atime; }
|
||||||
|
public get mtime(): Date { return this.stats.mtime; }
|
||||||
|
public get ctime(): Date { return this.stats.ctime; }
|
||||||
|
public get birthtime(): Date { return this.stats.birthtime; }
|
||||||
|
public get atimeMs(): number { return this.stats.atimeMs; }
|
||||||
|
public get mtimeMs(): number { return this.stats.mtimeMs; }
|
||||||
|
public get ctimeMs(): number { return this.stats.ctimeMs; }
|
||||||
|
public get birthtimeMs(): number { return this.stats.birthtimeMs; }
|
||||||
|
public isFile(): boolean { return this.stats._isFile; }
|
||||||
|
public isDirectory(): boolean { return this.stats._isDirectory; }
|
||||||
|
public isBlockDevice(): boolean { return this.stats._isBlockDevice; }
|
||||||
|
public isCharacterDevice(): boolean { return this.stats._isCharacterDevice; }
|
||||||
|
public isSymbolicLink(): boolean { return this.stats._isSymbolicLink; }
|
||||||
|
public isFIFO(): boolean { return this.stats._isFIFO; }
|
||||||
|
public isSocket(): boolean { return this.stats._isSocket; }
|
||||||
|
|
||||||
|
public toObject(): object {
|
||||||
|
return JSON.parse(JSON.stringify(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/protocol/src/browser/modules/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from "./child_process";
|
||||||
|
export * from "./fs";
|
||||||
|
export * from "./net";
|
||||||
|
export * from "./node-pty";
|
||||||
|
export * from "./spdlog";
|
||||||
|
export * from "./trash";
|
||||||
296
packages/protocol/src/browser/modules/net.ts
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
import * as net from "net";
|
||||||
|
import { callbackify } from "util";
|
||||||
|
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
|
||||||
|
import { NetModuleProxy, NetServerProxy, NetSocketProxy } from "../../node/modules/net";
|
||||||
|
import { Duplex } from "./stream";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
interface ClientNetSocketProxy extends NetSocketProxy, ClientServerProxy<net.Socket> {}
|
||||||
|
|
||||||
|
export class Socket extends Duplex<ClientNetSocketProxy> implements net.Socket {
|
||||||
|
private _connecting: boolean = false;
|
||||||
|
private _destroyed: boolean = false;
|
||||||
|
|
||||||
|
public constructor(proxyPromise: Promise<ClientNetSocketProxy> | ClientNetSocketProxy, connecting?: boolean) {
|
||||||
|
super(proxyPromise);
|
||||||
|
if (connecting) {
|
||||||
|
this._connecting = connecting;
|
||||||
|
}
|
||||||
|
this.on("close", () => {
|
||||||
|
this._destroyed = true;
|
||||||
|
this._connecting = false;
|
||||||
|
});
|
||||||
|
this.on("connect", () => this._connecting = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public connect(options: number | string | net.SocketConnectOpts, host?: string | Function, callback?: Function): this {
|
||||||
|
if (typeof host === "function") {
|
||||||
|
callback = host;
|
||||||
|
host = undefined;
|
||||||
|
}
|
||||||
|
this._connecting = true;
|
||||||
|
if (callback) {
|
||||||
|
this.on("connect", callback as () => void);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.catch(this.proxy.connect(options, host));
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line no-any
|
||||||
|
public end(data?: any, encoding?: string | Function, callback?: Function): void {
|
||||||
|
if (typeof encoding === "function") {
|
||||||
|
callback = encoding;
|
||||||
|
encoding = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackify(this.proxy.end)(data, encoding, () => {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line no-any
|
||||||
|
public write(data: any, encoding?: string | Function, fd?: string | Function): boolean {
|
||||||
|
let callback: undefined | Function;
|
||||||
|
if (typeof encoding === "function") {
|
||||||
|
callback = encoding;
|
||||||
|
encoding = undefined;
|
||||||
|
}
|
||||||
|
if (typeof fd === "function") {
|
||||||
|
callback = fd;
|
||||||
|
fd = undefined;
|
||||||
|
}
|
||||||
|
if (typeof fd !== "undefined") {
|
||||||
|
throw new Error("fd argument not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackify(this.proxy.write)(data, encoding, () => {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true; // Always true since we can't get this synchronously.
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connecting(): boolean {
|
||||||
|
return this._connecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get destroyed(): boolean {
|
||||||
|
return this._destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get bufferSize(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get bytesRead(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get bytesWritten(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get localAddress(): string {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get localPort(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public address(): net.AddressInfo | string {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public setTimeout(): this {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public setNoDelay(): this {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public setKeepAlive(): this {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public unref(): void {
|
||||||
|
this.catch(this.proxy.unref());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref(): void {
|
||||||
|
this.catch(this.proxy.ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientNetServerProxy extends NetServerProxy, ClientServerProxy<net.Server> {
|
||||||
|
onConnection(cb: (proxy: ClientNetSocketProxy) => void): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Server extends ClientProxy<ClientNetServerProxy> implements net.Server {
|
||||||
|
private socketId = 0;
|
||||||
|
private readonly sockets = new Map<number, net.Socket>();
|
||||||
|
private _listening: boolean = false;
|
||||||
|
|
||||||
|
public constructor(proxyPromise: Promise<ClientNetServerProxy> | ClientNetServerProxy) {
|
||||||
|
super(proxyPromise);
|
||||||
|
|
||||||
|
this.catch(this.proxy.onConnection((socketProxy) => {
|
||||||
|
const socket = new Socket(socketProxy);
|
||||||
|
const socketId = this.socketId++;
|
||||||
|
this.sockets.set(socketId, socket);
|
||||||
|
socket.on("error", () => this.sockets.delete(socketId));
|
||||||
|
socket.on("close", () => this.sockets.delete(socketId));
|
||||||
|
this.emit("connection", socket);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.on("listening", () => this._listening = true);
|
||||||
|
this.on("error", () => this._listening = false);
|
||||||
|
this.on("close", () => this._listening = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public listen(handle?: net.ListenOptions | number | string, hostname?: string | number | Function, backlog?: number | Function, callback?: Function): this {
|
||||||
|
if (typeof hostname === "function") {
|
||||||
|
callback = hostname;
|
||||||
|
hostname = undefined;
|
||||||
|
}
|
||||||
|
if (typeof backlog === "function") {
|
||||||
|
callback = backlog;
|
||||||
|
backlog = undefined;
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
this.on("listening", callback as () => void);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.catch(this.proxy.listen(handle, hostname, backlog));
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connections(): number {
|
||||||
|
return this.sockets.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get listening(): boolean {
|
||||||
|
return this._listening;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get maxConnections(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public address(): net.AddressInfo | string {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(callback?: () => void): this {
|
||||||
|
this._listening = false;
|
||||||
|
if (callback) {
|
||||||
|
this.on("close", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.catch(this.proxy.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref(): this {
|
||||||
|
return this.catch(this.proxy.ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
public unref(): this {
|
||||||
|
return this.catch(this.proxy.unref());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnections(cb: (error: Error | null, count: number) => void): void {
|
||||||
|
cb(null, this.sockets.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleDisconnect(): void {
|
||||||
|
this.emit("close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeNet = typeof net;
|
||||||
|
|
||||||
|
interface ClientNetModuleProxy extends NetModuleProxy, ClientServerProxy {
|
||||||
|
createSocket(options?: net.SocketConstructorOpts): Promise<ClientNetSocketProxy>;
|
||||||
|
createConnection(target: string | number | net.NetConnectOpts, host?: string): Promise<ClientNetSocketProxy>;
|
||||||
|
createServer(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean }): Promise<ClientNetServerProxy>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NetModule implements NodeNet {
|
||||||
|
public readonly Socket: typeof net.Socket;
|
||||||
|
public readonly Server: typeof net.Server;
|
||||||
|
|
||||||
|
public constructor(private readonly proxy: ClientNetModuleProxy) {
|
||||||
|
// @ts-ignore this is because Socket is missing things from the Stream
|
||||||
|
// namespace but I'm unsure how best to provide them (finished,
|
||||||
|
// finished.__promisify__, pipeline, and some others) or if it even matters.
|
||||||
|
this.Socket = class extends Socket {
|
||||||
|
public constructor(options?: net.SocketConstructorOpts) {
|
||||||
|
super(proxy.createSocket(options));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Server = class extends Server {
|
||||||
|
public constructor(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: Socket) => void), listener?: (socket: Socket) => void) {
|
||||||
|
super(proxy.createServer(typeof options !== "function" ? options : undefined));
|
||||||
|
if (typeof options === "function") {
|
||||||
|
listener = options;
|
||||||
|
}
|
||||||
|
if (listener) {
|
||||||
|
this.on("connection", listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public createConnection = (target: string | number | net.NetConnectOpts, host?: string | Function, callback?: Function): net.Socket => {
|
||||||
|
if (typeof host === "function") {
|
||||||
|
callback = host;
|
||||||
|
host = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const socket = new Socket(this.proxy.createConnection(target, host), true);
|
||||||
|
if (callback) {
|
||||||
|
socket.on("connect", callback as () => void);
|
||||||
|
}
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createServer = (
|
||||||
|
options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
|
||||||
|
callback?: (socket: net.Socket) => void,
|
||||||
|
): net.Server => {
|
||||||
|
if (typeof options === "function") {
|
||||||
|
callback = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = new Server(this.proxy.createServer(options));
|
||||||
|
if (callback) {
|
||||||
|
server.on("connection", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public connect = (): net.Socket => {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public isIP = (_input: string): number => {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public isIPv4 = (_input: string): boolean => {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public isIPv6 = (_input: string): boolean => {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
79
packages/protocol/src/browser/modules/node-pty.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import * as pty from "node-pty";
|
||||||
|
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
|
||||||
|
import { NodePtyModuleProxy, NodePtyProcessProxy } from "../../node/modules/node-pty";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
interface ClientNodePtyProcessProxy extends NodePtyProcessProxy, ClientServerProxy {}
|
||||||
|
|
||||||
|
export class NodePtyProcess extends ClientProxy<ClientNodePtyProcessProxy> implements pty.IPty {
|
||||||
|
private _pid = -1;
|
||||||
|
private _process = "";
|
||||||
|
private lastCols: number | undefined;
|
||||||
|
private lastRows: number | undefined;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly moduleProxy: ClientNodePtyModuleProxy,
|
||||||
|
private readonly file: string,
|
||||||
|
private readonly args: string[] | string,
|
||||||
|
private readonly options: pty.IPtyForkOptions,
|
||||||
|
) {
|
||||||
|
super(moduleProxy.spawn(file, args, options));
|
||||||
|
this.on("process", (process) => this._process = process);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected initialize(proxyPromise: Promise<ClientNodePtyProcessProxy>): ClientNodePtyProcessProxy {
|
||||||
|
const proxy = super.initialize(proxyPromise);
|
||||||
|
this.catch(this.proxy.getPid().then((p) => this._pid = p));
|
||||||
|
this.catch(this.proxy.getProcess().then((p) => this._process = p));
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get pid(): number {
|
||||||
|
return this._pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get process(): string {
|
||||||
|
return this._process;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(columns: number, rows: number): void {
|
||||||
|
this.lastCols = columns;
|
||||||
|
this.lastRows = rows;
|
||||||
|
|
||||||
|
this.catch(this.proxy.resize(columns, rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
public write(data: string): void {
|
||||||
|
this.catch(this.proxy.write(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public kill(signal?: string): void {
|
||||||
|
this.catch(this.proxy.kill(signal));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleDisconnect(): void {
|
||||||
|
this._process += " (disconnected)";
|
||||||
|
this.emit("data", "\r\n\nLost connection...\r\n\n");
|
||||||
|
this.initialize(this.moduleProxy.spawn(this.file, this.args, {
|
||||||
|
...this.options,
|
||||||
|
cols: this.lastCols || this.options.cols,
|
||||||
|
rows: this.lastRows || this.options.rows,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodePty = typeof pty;
|
||||||
|
|
||||||
|
interface ClientNodePtyModuleProxy extends NodePtyModuleProxy, ClientServerProxy {
|
||||||
|
spawn(file: string, args: string[] | string, options: pty.IPtyForkOptions): Promise<ClientNodePtyProcessProxy>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodePtyModule implements NodePty {
|
||||||
|
public constructor(private readonly proxy: ClientNodePtyModuleProxy) {}
|
||||||
|
|
||||||
|
public spawn = (file: string, args: string[] | string, options: pty.IPtyForkOptions): pty.IPty => {
|
||||||
|
return new NodePtyProcess(this.proxy, file, args, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
packages/protocol/src/browser/modules/spdlog.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import * as spdlog from "spdlog";
|
||||||
|
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
|
||||||
|
import { RotatingLoggerProxy, SpdlogModuleProxy } from "../../node/modules/spdlog";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
interface ClientRotatingLoggerProxy extends RotatingLoggerProxy, ClientServerProxy {}
|
||||||
|
|
||||||
|
class RotatingLogger extends ClientProxy<ClientRotatingLoggerProxy> implements spdlog.RotatingLogger {
|
||||||
|
public constructor(
|
||||||
|
private readonly moduleProxy: ClientSpdlogModuleProxy,
|
||||||
|
private readonly name: string,
|
||||||
|
private readonly filename: string,
|
||||||
|
private readonly filesize: number,
|
||||||
|
private readonly filecount: number,
|
||||||
|
) {
|
||||||
|
super(moduleProxy.createLogger(name, filename, filesize, filecount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public trace (message: string): void { this.catch(this.proxy.trace(message)); }
|
||||||
|
public debug (message: string): void { this.catch(this.proxy.debug(message)); }
|
||||||
|
public info (message: string): void { this.catch(this.proxy.info(message)); }
|
||||||
|
public warn (message: string): void { this.catch(this.proxy.warn(message)); }
|
||||||
|
public error (message: string): void { this.catch(this.proxy.error(message)); }
|
||||||
|
public critical (message: string): void { this.catch(this.proxy.critical(message)); }
|
||||||
|
public setLevel (level: number): void { this.catch(this.proxy.setLevel(level)); }
|
||||||
|
public clearFormatters (): void { this.catch(this.proxy.clearFormatters()); }
|
||||||
|
public flush (): void { this.catch(this.proxy.flush()); }
|
||||||
|
public drop (): void { this.catch(this.proxy.drop()); }
|
||||||
|
|
||||||
|
protected handleDisconnect(): void {
|
||||||
|
this.initialize(this.moduleProxy.createLogger(this.name, this.filename, this.filesize, this.filecount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientSpdlogModuleProxy extends SpdlogModuleProxy, ClientServerProxy {
|
||||||
|
createLogger(name: string, filePath: string, fileSize: number, fileCount: number): Promise<ClientRotatingLoggerProxy>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SpdlogModule {
|
||||||
|
public readonly RotatingLogger: typeof spdlog.RotatingLogger;
|
||||||
|
|
||||||
|
public constructor(private readonly proxy: ClientSpdlogModuleProxy) {
|
||||||
|
this.RotatingLogger = class extends RotatingLogger {
|
||||||
|
public constructor(name: string, filename: string, filesize: number, filecount: number) {
|
||||||
|
super(proxy, name, filename, filesize, filecount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public setAsyncMode = (bufferSize: number, flushInterval: number): Promise<void> => {
|
||||||
|
return this.proxy.setAsyncMode(bufferSize, flushInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): RotatingLogger {
|
||||||
|
return new RotatingLogger(this.proxy, name, filename, filesize, filecount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createRotatingLoggerAsync(name: string, filename: string, filesize: number, filecount: number): Promise<RotatingLogger> {
|
||||||
|
return Promise.resolve(this.createRotatingLogger(name, filename, filesize, filecount));
|
||||||
|
}
|
||||||
|
}
|
||||||
257
packages/protocol/src/browser/modules/stream.ts
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
import * as stream from "stream";
|
||||||
|
import { callbackify } from "util";
|
||||||
|
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
|
||||||
|
import { isPromise } from "../../common/util";
|
||||||
|
import { DuplexProxy, ReadableProxy, WritableProxy } from "../../node/modules/stream";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs no-any
|
||||||
|
|
||||||
|
export interface ClientWritableProxy extends WritableProxy, ClientServerProxy<stream.Writable> {}
|
||||||
|
|
||||||
|
export class Writable<T extends ClientWritableProxy = ClientWritableProxy> extends ClientProxy<T> implements stream.Writable {
|
||||||
|
public get writable(): boolean {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get writableHighWaterMark(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get writableLength(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public _write(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public _destroy(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public _final(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public pipe<T>(): T {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public cork(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public uncork(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
this.catch(this.proxy.destroy());
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDefaultEncoding(encoding: string): this {
|
||||||
|
return this.catch(this.proxy.setDefaultEncoding(encoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
public write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
|
||||||
|
if (typeof encoding === "function") {
|
||||||
|
callback = encoding;
|
||||||
|
encoding = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.write)(chunk, encoding, (error) => {
|
||||||
|
if (callback) {
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true; // Always true since we can't get this synchronously.
|
||||||
|
}
|
||||||
|
|
||||||
|
public end(data?: any | (() => void), encoding?: string | (() => void), callback?: (() => void)): void {
|
||||||
|
if (typeof data === "function") {
|
||||||
|
callback = data;
|
||||||
|
data = undefined;
|
||||||
|
}
|
||||||
|
if (typeof encoding === "function") {
|
||||||
|
callback = encoding;
|
||||||
|
encoding = undefined;
|
||||||
|
}
|
||||||
|
callbackify(this.proxy.end)(data, encoding, () => {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleDisconnect(): void {
|
||||||
|
this.emit("close");
|
||||||
|
this.emit("finish");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientReadableProxy extends ReadableProxy, ClientServerProxy<stream.Readable> {}
|
||||||
|
|
||||||
|
export class Readable<T extends ClientReadableProxy = ClientReadableProxy> extends ClientProxy<T> implements stream.Readable {
|
||||||
|
public get readable(): boolean {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get readableHighWaterMark(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get readableLength(): number {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public _read(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public read(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public _destroy(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public unpipe(): this {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause(): this {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public resume(): this {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public isPaused(): boolean {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public wrap(): this {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public push(): boolean {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public unshift(): void {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public pipe<P extends NodeJS.WritableStream>(destination: P, options?: { end?: boolean }): P {
|
||||||
|
const writableProxy = (destination as any as Writable).proxyPromise;
|
||||||
|
if (!writableProxy) {
|
||||||
|
throw new Error("can only pipe stream proxies");
|
||||||
|
}
|
||||||
|
this.catch(
|
||||||
|
isPromise(writableProxy)
|
||||||
|
? writableProxy.then((p) => this.proxy.pipe(p, options))
|
||||||
|
: this.proxy.pipe(writableProxy, options),
|
||||||
|
);
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public [Symbol.asyncIterator](): AsyncIterableIterator<any> {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
this.catch(this.proxy.destroy());
|
||||||
|
}
|
||||||
|
|
||||||
|
public setEncoding(encoding: string): this {
|
||||||
|
return this.catch(this.proxy.setEncoding(encoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleDisconnect(): void {
|
||||||
|
this.emit("close");
|
||||||
|
this.emit("end");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientDuplexProxy extends DuplexProxy, ClientServerProxy<stream.Duplex> {}
|
||||||
|
|
||||||
|
export class Duplex<T extends ClientDuplexProxy = ClientDuplexProxy> extends Writable<T> implements stream.Duplex, stream.Readable {
|
||||||
|
private readonly _readable: Readable;
|
||||||
|
|
||||||
|
public constructor(proxyPromise: Promise<T> | T) {
|
||||||
|
super(proxyPromise);
|
||||||
|
this._readable = new Readable(proxyPromise, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get readable(): boolean {
|
||||||
|
return this._readable.readable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get readableHighWaterMark(): number {
|
||||||
|
return this._readable.readableHighWaterMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get readableLength(): number {
|
||||||
|
return this._readable.readableLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public _read(): void {
|
||||||
|
this._readable._read();
|
||||||
|
}
|
||||||
|
|
||||||
|
public read(): void {
|
||||||
|
this._readable.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unpipe(): this {
|
||||||
|
this._readable.unpipe();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause(): this {
|
||||||
|
this._readable.unpipe();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resume(): this {
|
||||||
|
this._readable.resume();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isPaused(): boolean {
|
||||||
|
return this._readable.isPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
public wrap(): this {
|
||||||
|
this._readable.wrap();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public push(): boolean {
|
||||||
|
return this._readable.push();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unshift(): void {
|
||||||
|
this._readable.unshift();
|
||||||
|
}
|
||||||
|
|
||||||
|
public [Symbol.asyncIterator](): AsyncIterableIterator<any> {
|
||||||
|
return this._readable[Symbol.asyncIterator]();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setEncoding(encoding: string): this {
|
||||||
|
return this.catch(this.proxy.setEncoding(encoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleDisconnect(): void {
|
||||||
|
super.handleDisconnect();
|
||||||
|
this.emit("end");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
packages/protocol/src/browser/modules/trash.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as trash from "trash";
|
||||||
|
import { ClientServerProxy } from "../../common/proxy";
|
||||||
|
import { TrashModuleProxy } from "../../node/modules/trash";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
interface ClientTrashModuleProxy extends TrashModuleProxy, ClientServerProxy {}
|
||||||
|
|
||||||
|
export class TrashModule {
|
||||||
|
public constructor(private readonly proxy: ClientTrashModuleProxy) {}
|
||||||
|
|
||||||
|
public trash = (path: string, options?: trash.Options): Promise<void> => {
|
||||||
|
return this.proxy.trash(path, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ export interface SendableConnection {
|
|||||||
export interface ReadWriteConnection extends SendableConnection {
|
export interface ReadWriteConnection extends SendableConnection {
|
||||||
onMessage(cb: (data: Uint8Array | Buffer) => void): void;
|
onMessage(cb: (data: Uint8Array | Buffer) => void): void;
|
||||||
onClose(cb: () => void): void;
|
onClose(cb: () => void): void;
|
||||||
|
onDown(cb: () => void): void;
|
||||||
|
onUp(cb: () => void): void;
|
||||||
close(): void;
|
close(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +23,10 @@ export interface InitData {
|
|||||||
readonly homeDirectory: string;
|
readonly homeDirectory: string;
|
||||||
readonly tmpDirectory: string;
|
readonly tmpDirectory: string;
|
||||||
readonly shell: string;
|
readonly shell: string;
|
||||||
|
readonly extensionsDirectory: string;
|
||||||
readonly builtInExtensionsDirectory: string;
|
readonly builtInExtensionsDirectory: string;
|
||||||
|
readonly extraExtensionDirectories: string[];
|
||||||
|
readonly extraBuiltinExtensionDirectories: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SharedProcessData {
|
export interface SharedProcessData {
|
||||||
|
|||||||
@@ -1,418 +0,0 @@
|
|||||||
/// <reference path="../../../../lib/vscode/src/typings/spdlog.d.ts" />
|
|
||||||
import { ChildProcess, SpawnOptions, ForkOptions } from "child_process";
|
|
||||||
import { EventEmitter } from "events";
|
|
||||||
import { Socket } from "net";
|
|
||||||
import { Duplex, Readable, Writable } from "stream";
|
|
||||||
import { IDisposable } from "@coder/disposable";
|
|
||||||
import { logger } from "@coder/logger";
|
|
||||||
|
|
||||||
// tslint:disable no-any
|
|
||||||
|
|
||||||
export type ForkProvider = (modulePath: string, args: string[], options: ForkOptions) => ChildProcess;
|
|
||||||
|
|
||||||
export interface Disposer extends IDisposable {
|
|
||||||
onDidDispose: (cb: () => void) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ActiveEvalEmitter {
|
|
||||||
removeAllListeners(event?: string): void;
|
|
||||||
emit(event: string, ...args: any[]): void;
|
|
||||||
on(event: string, cb: (...args: any[]) => void): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for server-side evaluations.
|
|
||||||
*/
|
|
||||||
export class EvalHelper {
|
|
||||||
// For any non-external modules that are not built in, we need to require and
|
|
||||||
// access them here. A require on the client-side won't work since that code
|
|
||||||
// won't exist on the server (and bloat the client with an unused import), and
|
|
||||||
// we can't manually import on the server-side and then call
|
|
||||||
// `__webpack_require__` on the client-side because Webpack stores modules by
|
|
||||||
// their paths which would require us to hard-code the path. These aren't
|
|
||||||
// required immediately so we have a chance to unpack the .node files and set
|
|
||||||
// their locations.
|
|
||||||
public modules = {
|
|
||||||
spdlog: require("spdlog") as typeof import("spdlog"),
|
|
||||||
pty: require("node-pty-prebuilt") as typeof import("node-pty"),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some spawn code tries to preserve the env (the debug adapter for instance)
|
|
||||||
* but the env is mostly blank (since we're in the browser), so we'll just
|
|
||||||
* always preserve the main process.env here, otherwise it won't have access
|
|
||||||
* to PATH, etc.
|
|
||||||
* TODO: An alternative solution would be to send the env to the browser?
|
|
||||||
*/
|
|
||||||
public preserveEnv(options: SpawnOptions | ForkOptions): void {
|
|
||||||
if (options && options.env) {
|
|
||||||
options.env = { ...process.env, ...options.env };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for client-side active evaluations.
|
|
||||||
*/
|
|
||||||
export class ActiveEvalHelper implements ActiveEvalEmitter {
|
|
||||||
public constructor(private readonly emitter: ActiveEvalEmitter) {}
|
|
||||||
|
|
||||||
public removeAllListeners(event?: string): void {
|
|
||||||
this.emitter.removeAllListeners(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public emit(event: string, ...args: any[]): void {
|
|
||||||
this.emitter.emit(event, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public on(event: string, cb: (...args: any[]) => void): void {
|
|
||||||
this.emitter.on(event, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new helper to make unique events for an item.
|
|
||||||
*/
|
|
||||||
public createUnique(id: number | "stdout" | "stderr" | "stdin"): ActiveEvalHelper {
|
|
||||||
return new ActiveEvalHelper(this.createUniqueEmitter(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap the evaluation emitter to make unique events for an item to prevent
|
|
||||||
* conflicts when it shares that emitter with other items.
|
|
||||||
*/
|
|
||||||
protected createUniqueEmitter(id: number | "stdout" | "stderr" | "stdin"): ActiveEvalEmitter {
|
|
||||||
let events = <string[]>[];
|
|
||||||
|
|
||||||
return {
|
|
||||||
removeAllListeners: (event?: string): void => {
|
|
||||||
if (!event) {
|
|
||||||
events.forEach((e) => this.removeAllListeners(e));
|
|
||||||
events = [];
|
|
||||||
} else {
|
|
||||||
const index = events.indexOf(event);
|
|
||||||
if (index !== -1) {
|
|
||||||
events.splice(index, 1);
|
|
||||||
this.removeAllListeners(`${event}:${id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emit: (event: string, ...args: any[]): void => {
|
|
||||||
this.emit(`${event}:${id}`, ...args);
|
|
||||||
},
|
|
||||||
on: (event: string, cb: (...args: any[]) => void): void => {
|
|
||||||
if (!events.includes(event)) {
|
|
||||||
events.push(event);
|
|
||||||
}
|
|
||||||
this.on(`${event}:${id}`, cb);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for server-side active evaluations.
|
|
||||||
*/
|
|
||||||
export class ServerActiveEvalHelper extends ActiveEvalHelper implements EvalHelper {
|
|
||||||
private readonly evalHelper = new EvalHelper();
|
|
||||||
public modules = this.evalHelper.modules;
|
|
||||||
|
|
||||||
public constructor(emitter: ActiveEvalEmitter, public readonly fork: ForkProvider) {
|
|
||||||
super(emitter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public preserveEnv(options: SpawnOptions | ForkOptions): void {
|
|
||||||
this.evalHelper.preserveEnv(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is a callback ID, return a function that emits the callback event
|
|
||||||
* on the active evaluation with that ID and all arguments passed to it.
|
|
||||||
* Otherwise, return undefined.
|
|
||||||
*/
|
|
||||||
public maybeCallback(callbackId?: number): ((...args: any[]) => void) | undefined {
|
|
||||||
return typeof callbackId !== "undefined" ? (...args: any[]): void => {
|
|
||||||
this.emit("callback", callbackId, ...args);
|
|
||||||
} : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind a socket to an active evaluation and returns a disposer.
|
|
||||||
*/
|
|
||||||
public bindSocket(socket: Socket): Disposer {
|
|
||||||
socket.on("connect", () => this.emit("connect"));
|
|
||||||
socket.on("lookup", (error, address, family, host) => this.emit("lookup", error, address, family, host));
|
|
||||||
socket.on("timeout", () => this.emit("timeout"));
|
|
||||||
|
|
||||||
this.on("connect", (options, callbackId) => socket.connect(options, this.maybeCallback(callbackId)));
|
|
||||||
this.on("ref", () => socket.ref());
|
|
||||||
this.on("setKeepAlive", (enable, initialDelay) => socket.setKeepAlive(enable, initialDelay));
|
|
||||||
this.on("setNoDelay", (noDelay) => socket.setNoDelay(noDelay));
|
|
||||||
this.on("setTimeout", (timeout, callbackId) => socket.setTimeout(timeout, this.maybeCallback(callbackId)));
|
|
||||||
this.on("unref", () => socket.unref());
|
|
||||||
|
|
||||||
this.bindReadable(socket);
|
|
||||||
this.bindWritable(socket);
|
|
||||||
|
|
||||||
return {
|
|
||||||
onDidDispose: (cb): Socket => socket.on("close", cb),
|
|
||||||
dispose: (): void => {
|
|
||||||
socket.removeAllListeners();
|
|
||||||
socket.end();
|
|
||||||
socket.destroy();
|
|
||||||
socket.unref();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind a writable stream to the active evaluation.
|
|
||||||
*/
|
|
||||||
public bindWritable(writable: Writable | Duplex): void {
|
|
||||||
if (!((writable as Readable).read)) { // To avoid binding twice.
|
|
||||||
writable.on("close", () => this.emit("close"));
|
|
||||||
writable.on("error", (error) => this.emit("error", error));
|
|
||||||
|
|
||||||
this.on("destroy", () => writable.destroy());
|
|
||||||
}
|
|
||||||
|
|
||||||
writable.on("drain", () => this.emit("drain"));
|
|
||||||
writable.on("finish", () => this.emit("finish"));
|
|
||||||
writable.on("pipe", () => this.emit("pipe"));
|
|
||||||
writable.on("unpipe", () => this.emit("unpipe"));
|
|
||||||
|
|
||||||
this.on("cork", () => writable.cork());
|
|
||||||
this.on("end", (chunk, encoding, callbackId) => writable.end(chunk, encoding, this.maybeCallback(callbackId)));
|
|
||||||
this.on("setDefaultEncoding", (encoding) => writable.setDefaultEncoding(encoding));
|
|
||||||
this.on("uncork", () => writable.uncork());
|
|
||||||
// Sockets can pass an fd instead of a callback but streams cannot.
|
|
||||||
this.on("write", (chunk, encoding, fd, callbackId) => writable.write(chunk, encoding, this.maybeCallback(callbackId) || fd));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind a readable stream to the active evaluation.
|
|
||||||
*/
|
|
||||||
public bindReadable(readable: Readable): void {
|
|
||||||
// Streams don't have an argument on close but sockets do.
|
|
||||||
readable.on("close", (...args: any[]) => this.emit("close", ...args));
|
|
||||||
readable.on("data", (data) => this.emit("data", data));
|
|
||||||
readable.on("end", () => this.emit("end"));
|
|
||||||
readable.on("error", (error) => this.emit("error", error));
|
|
||||||
readable.on("readable", () => this.emit("readable"));
|
|
||||||
|
|
||||||
this.on("destroy", () => readable.destroy());
|
|
||||||
this.on("pause", () => readable.pause());
|
|
||||||
this.on("push", (chunk, encoding) => readable.push(chunk, encoding));
|
|
||||||
this.on("resume", () => readable.resume());
|
|
||||||
this.on("setEncoding", (encoding) => readable.setEncoding(encoding));
|
|
||||||
this.on("unshift", (chunk) => readable.unshift(chunk));
|
|
||||||
}
|
|
||||||
|
|
||||||
public createUnique(id: number | "stdout" | "stderr" | "stdin"): ServerActiveEvalHelper {
|
|
||||||
return new ServerActiveEvalHelper(this.createUniqueEmitter(id), this.fork);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event emitter that can store callbacks with IDs in a map so we can pass
|
|
||||||
* them back and forth through an active evaluation using those IDs.
|
|
||||||
*/
|
|
||||||
export class CallbackEmitter extends EventEmitter {
|
|
||||||
private _ae: ActiveEvalHelper | undefined;
|
|
||||||
private callbackId = 0;
|
|
||||||
private readonly callbacks = new Map<number, Function>();
|
|
||||||
|
|
||||||
public constructor(ae?: ActiveEvalHelper) {
|
|
||||||
super();
|
|
||||||
if (ae) {
|
|
||||||
this.ae = ae;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get ae(): ActiveEvalHelper {
|
|
||||||
if (!this._ae) {
|
|
||||||
throw new Error("trying to access active evaluation before it has been set");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._ae;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected set ae(ae: ActiveEvalHelper) {
|
|
||||||
if (this._ae) {
|
|
||||||
throw new Error("cannot override active evaluation");
|
|
||||||
}
|
|
||||||
this._ae = ae;
|
|
||||||
this.ae.on("callback", (callbackId, ...args: any[]) => this.runCallback(callbackId, ...args));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the callback and return and ID referencing its location in the map.
|
|
||||||
*/
|
|
||||||
protected storeCallback(callback?: Function): number | undefined {
|
|
||||||
if (!callback) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const callbackId = this.callbackId++;
|
|
||||||
this.callbacks.set(callbackId, callback);
|
|
||||||
|
|
||||||
return callbackId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call the function with the specified ID and delete it from the map.
|
|
||||||
* If the ID is undefined or doesn't exist, nothing happens.
|
|
||||||
*/
|
|
||||||
private runCallback(callbackId?: number, ...args: any[]): void {
|
|
||||||
const callback = typeof callbackId !== "undefined" && this.callbacks.get(callbackId);
|
|
||||||
if (callback && typeof callbackId !== "undefined") {
|
|
||||||
this.callbacks.delete(callbackId);
|
|
||||||
callback(...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A writable stream over an active evaluation.
|
|
||||||
*/
|
|
||||||
export class ActiveEvalWritable extends CallbackEmitter implements Writable {
|
|
||||||
public constructor(ae: ActiveEvalHelper) {
|
|
||||||
super(ae);
|
|
||||||
// Streams don't have an argument on close but sockets do.
|
|
||||||
this.ae.on("close", (...args: any[]) => this.emit("close", ...args));
|
|
||||||
this.ae.on("drain", () => this.emit("drain"));
|
|
||||||
this.ae.on("error", (error) => this.emit("error", error));
|
|
||||||
this.ae.on("finish", () => this.emit("finish"));
|
|
||||||
this.ae.on("pipe", () => logger.warn("pipe is not supported"));
|
|
||||||
this.ae.on("unpipe", () => logger.warn("unpipe is not supported"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public get writable(): boolean { throw new Error("not implemented"); }
|
|
||||||
public get writableHighWaterMark(): number { throw new Error("not implemented"); }
|
|
||||||
public get writableLength(): number { throw new Error("not implemented"); }
|
|
||||||
public _write(): void { throw new Error("not implemented"); }
|
|
||||||
public _destroy(): void { throw new Error("not implemented"); }
|
|
||||||
public _final(): void { throw new Error("not implemented"); }
|
|
||||||
public pipe<T>(): T { throw new Error("not implemented"); }
|
|
||||||
|
|
||||||
public cork(): void { this.ae.emit("cork"); }
|
|
||||||
public destroy(): void { this.ae.emit("destroy"); }
|
|
||||||
public setDefaultEncoding(encoding: string): this {
|
|
||||||
this.ae.emit("setDefaultEncoding", encoding);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public uncork(): void { this.ae.emit("uncork"); }
|
|
||||||
|
|
||||||
public write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
|
|
||||||
if (typeof encoding === "function") {
|
|
||||||
callback = encoding;
|
|
||||||
encoding = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sockets can pass an fd instead of a callback but streams cannot..
|
|
||||||
this.ae.emit("write", chunk, encoding, undefined, this.storeCallback(callback));
|
|
||||||
|
|
||||||
// Always true since we can't get this synchronously.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public end(data?: any, encoding?: string | Function, callback?: Function): void {
|
|
||||||
if (typeof encoding === "function") {
|
|
||||||
callback = encoding;
|
|
||||||
encoding = undefined;
|
|
||||||
}
|
|
||||||
this.ae.emit("end", data, encoding, this.storeCallback(callback));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A readable stream over an active evaluation.
|
|
||||||
*/
|
|
||||||
export class ActiveEvalReadable extends CallbackEmitter implements Readable {
|
|
||||||
public constructor(ae: ActiveEvalHelper) {
|
|
||||||
super(ae);
|
|
||||||
this.ae.on("close", () => this.emit("close"));
|
|
||||||
this.ae.on("data", (data) => this.emit("data", data));
|
|
||||||
this.ae.on("end", () => this.emit("end"));
|
|
||||||
this.ae.on("error", (error) => this.emit("error", error));
|
|
||||||
this.ae.on("readable", () => this.emit("readable"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public get readable(): boolean { throw new Error("not implemented"); }
|
|
||||||
public get readableHighWaterMark(): number { throw new Error("not implemented"); }
|
|
||||||
public get readableLength(): number { throw new Error("not implemented"); }
|
|
||||||
public _read(): void { throw new Error("not implemented"); }
|
|
||||||
public read(): any { throw new Error("not implemented"); }
|
|
||||||
public isPaused(): boolean { throw new Error("not implemented"); }
|
|
||||||
public pipe<T>(): T { throw new Error("not implemented"); }
|
|
||||||
public unpipe(): this { throw new Error("not implemented"); }
|
|
||||||
public unshift(): this { throw new Error("not implemented"); }
|
|
||||||
public wrap(): this { throw new Error("not implemented"); }
|
|
||||||
public push(): boolean { throw new Error("not implemented"); }
|
|
||||||
public _destroy(): void { throw new Error("not implemented"); }
|
|
||||||
public [Symbol.asyncIterator](): AsyncIterableIterator<any> { throw new Error("not implemented"); }
|
|
||||||
|
|
||||||
public destroy(): void { this.ae.emit("destroy"); }
|
|
||||||
public pause(): this { return this.emitReturnThis("pause"); }
|
|
||||||
public resume(): this { return this.emitReturnThis("resume"); }
|
|
||||||
public setEncoding(encoding?: string): this { return this.emitReturnThis("setEncoding", encoding); }
|
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
protected emitReturnThis(event: string, ...args: any[]): this {
|
|
||||||
this.ae.emit(event, ...args);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An duplex stream over an active evaluation.
|
|
||||||
*/
|
|
||||||
export class ActiveEvalDuplex extends ActiveEvalReadable implements Duplex {
|
|
||||||
// Some unfortunate duplication here since we can't have multiple extends.
|
|
||||||
public constructor(ae: ActiveEvalHelper) {
|
|
||||||
super(ae);
|
|
||||||
this.ae.on("drain", () => this.emit("drain"));
|
|
||||||
this.ae.on("finish", () => this.emit("finish"));
|
|
||||||
this.ae.on("pipe", () => logger.warn("pipe is not supported"));
|
|
||||||
this.ae.on("unpipe", () => logger.warn("unpipe is not supported"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public get writable(): boolean { throw new Error("not implemented"); }
|
|
||||||
public get writableHighWaterMark(): number { throw new Error("not implemented"); }
|
|
||||||
public get writableLength(): number { throw new Error("not implemented"); }
|
|
||||||
public _write(): void { throw new Error("not implemented"); }
|
|
||||||
public _destroy(): void { throw new Error("not implemented"); }
|
|
||||||
public _final(): void { throw new Error("not implemented"); }
|
|
||||||
public pipe<T>(): T { throw new Error("not implemented"); }
|
|
||||||
|
|
||||||
public cork(): void { this.ae.emit("cork"); }
|
|
||||||
public destroy(): void { this.ae.emit("destroy"); }
|
|
||||||
public setDefaultEncoding(encoding: string): this {
|
|
||||||
this.ae.emit("setDefaultEncoding", encoding);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public uncork(): void { this.ae.emit("uncork"); }
|
|
||||||
|
|
||||||
public write(chunk: any, encoding?: string | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean {
|
|
||||||
if (typeof encoding === "function") {
|
|
||||||
callback = encoding;
|
|
||||||
encoding = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sockets can pass an fd instead of a callback but streams cannot..
|
|
||||||
this.ae.emit("write", chunk, encoding, undefined, this.storeCallback(callback));
|
|
||||||
|
|
||||||
// Always true since we can't get this synchronously.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public end(data?: any, encoding?: string | Function, callback?: Function): void {
|
|
||||||
if (typeof encoding === "function") {
|
|
||||||
callback = encoding;
|
|
||||||
encoding = undefined;
|
|
||||||
}
|
|
||||||
this.ae.emit("end", data, encoding, this.storeCallback(callback));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
325
packages/protocol/src/common/proxy.ts
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
import { EventEmitter } from "events";
|
||||||
|
import { isPromise, EventCallback } from "./util";
|
||||||
|
|
||||||
|
// tslint:disable no-any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow using a proxy like it's returned synchronously. This only works because
|
||||||
|
* all proxy methods must return promises.
|
||||||
|
*/
|
||||||
|
const unpromisify = <T extends ClientServerProxy>(proxyPromise: Promise<T>): T => {
|
||||||
|
return new Proxy({}, {
|
||||||
|
get: (target: any, name: string): any => {
|
||||||
|
if (typeof target[name] === "undefined") {
|
||||||
|
target[name] = async (...args: any[]): Promise<any> => {
|
||||||
|
const proxy = await proxyPromise;
|
||||||
|
|
||||||
|
return proxy ? (proxy as any)[name](...args) : undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return target[name];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client-side emitter that just forwards server proxy events to its own
|
||||||
|
* emitter. It also turns a promisified server proxy into a non-promisified
|
||||||
|
* proxy so we don't need a bunch of `then` calls everywhere.
|
||||||
|
*/
|
||||||
|
export abstract class ClientProxy<T extends ClientServerProxy> extends EventEmitter {
|
||||||
|
private _proxy: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can specify not to bind events in order to avoid emitting twice for
|
||||||
|
* duplex streams.
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private _proxyPromise: Promise<T> | T,
|
||||||
|
private readonly bindEvents: boolean = true,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this._proxy = this.initialize(this._proxyPromise);
|
||||||
|
if (this.bindEvents) {
|
||||||
|
this.on("disconnected", (error) => {
|
||||||
|
try {
|
||||||
|
this.emit("error", error);
|
||||||
|
} catch (error) {
|
||||||
|
// If nothing is listening, EventEmitter will throw an error.
|
||||||
|
}
|
||||||
|
this.handleDisconnect();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an event listener.
|
||||||
|
*/
|
||||||
|
public off(event: string, cb: (...args: any[]) => void): this {
|
||||||
|
// Fill it here because the fill we're using to provide EventEmitter for the
|
||||||
|
// browser doesn't appear to include `off`.
|
||||||
|
this.removeListener(event, cb);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind the event locally and ensure the event is bound on the server.
|
||||||
|
*/
|
||||||
|
public addListener(event: string, listener: (...args: any[]) => void): this {
|
||||||
|
this.catch(this.proxy.bindDelayedEvent(event));
|
||||||
|
|
||||||
|
return super.on(event, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for `addListener`.
|
||||||
|
*/
|
||||||
|
public on(event: string, listener: (...args: any[]) => void): this {
|
||||||
|
return this.addListener(event, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original promise for the server proxy. Can be used to be passed as an
|
||||||
|
* argument.
|
||||||
|
*/
|
||||||
|
public get proxyPromise(): Promise<T> | T {
|
||||||
|
return this._proxyPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server proxy.
|
||||||
|
*/
|
||||||
|
protected get proxy(): T {
|
||||||
|
return this._proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the proxy by unpromisifying if necessary and binding to its
|
||||||
|
* events.
|
||||||
|
*/
|
||||||
|
protected initialize(proxyPromise: Promise<T> | T): T {
|
||||||
|
this._proxyPromise = proxyPromise;
|
||||||
|
this._proxy = isPromise(this._proxyPromise)
|
||||||
|
? unpromisify(this._proxyPromise)
|
||||||
|
: this._proxyPromise;
|
||||||
|
if (this.bindEvents) {
|
||||||
|
this.proxy.onEvent((event, ...args): void => {
|
||||||
|
this.emit(event, ...args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform necessary cleanup on disconnect (or reconnect).
|
||||||
|
*/
|
||||||
|
protected abstract handleDisconnect(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an error event if the promise errors.
|
||||||
|
*/
|
||||||
|
protected catch(promise?: Promise<any>): this {
|
||||||
|
if (promise) {
|
||||||
|
promise.catch((e) => this.emit("error", e));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerProxyOptions<T> {
|
||||||
|
/**
|
||||||
|
* The events to bind immediately.
|
||||||
|
*/
|
||||||
|
bindEvents: string[];
|
||||||
|
/**
|
||||||
|
* Events that signal the proxy is done.
|
||||||
|
*/
|
||||||
|
doneEvents: string[];
|
||||||
|
/**
|
||||||
|
* Events that should only be bound when asked
|
||||||
|
*/
|
||||||
|
delayedEvents?: string[];
|
||||||
|
/**
|
||||||
|
* Whatever is emitting events (stream, child process, etc).
|
||||||
|
*/
|
||||||
|
instance: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual proxy instance on the server. Every method must only accept
|
||||||
|
* serializable arguments and must return promises with serializable values.
|
||||||
|
*
|
||||||
|
* If a proxy itself has proxies on creation (like how ChildProcess has stdin),
|
||||||
|
* then it should return all of those at once, otherwise you will miss events
|
||||||
|
* from those child proxies and fail to dispose them properly.
|
||||||
|
*
|
||||||
|
* Events listeners are added client-side (since all events automatically
|
||||||
|
* forward to the client), so onDone and onEvent do not need to be asynchronous.
|
||||||
|
*/
|
||||||
|
export abstract class ServerProxy<T extends EventEmitter = EventEmitter> {
|
||||||
|
public readonly instance: T;
|
||||||
|
|
||||||
|
private readonly callbacks = <EventCallback[]>[];
|
||||||
|
|
||||||
|
public constructor(private readonly options: ServerProxyOptions<T>) {
|
||||||
|
this.instance = options.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose the proxy.
|
||||||
|
*/
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.removeAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used instead of an event to force it to be implemented since there
|
||||||
|
* would be no guarantee the implementation would remember to emit the event.
|
||||||
|
*/
|
||||||
|
public onDone(cb: () => void): void {
|
||||||
|
this.options.doneEvents.forEach((event) => {
|
||||||
|
this.instance.on(event, cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind an event that will not fire without first binding it and shouldn't be
|
||||||
|
* bound immediately.
|
||||||
|
|
||||||
|
* For example, binding to `data` switches a stream to flowing mode, so we
|
||||||
|
* don't want to do it until we're asked. Otherwise something like `pipe`
|
||||||
|
* won't work because potentially some or all of the data will already have
|
||||||
|
* been flushed out.
|
||||||
|
*/
|
||||||
|
public async bindDelayedEvent(event: string): Promise<void> {
|
||||||
|
if (this.options.delayedEvents
|
||||||
|
&& this.options.delayedEvents.includes(event)
|
||||||
|
&& !this.options.bindEvents.includes(event)) {
|
||||||
|
this.options.bindEvents.push(event);
|
||||||
|
this.callbacks.forEach((cb) => {
|
||||||
|
this.instance.on(event, (...args: any[]) => cb(event, ...args));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to all possible events. On the client, this is to reduce boilerplate
|
||||||
|
* that would just be a bunch of error-prone forwarding of each individual
|
||||||
|
* event from the proxy to its own emitter.
|
||||||
|
*
|
||||||
|
* It also fixes a timing issue because we just always send all events from
|
||||||
|
* the server, so we never miss any due to listening too late.
|
||||||
|
*
|
||||||
|
* This cannot be async because then we can bind to the events too late.
|
||||||
|
*/
|
||||||
|
public onEvent(cb: EventCallback): void {
|
||||||
|
this.callbacks.push(cb);
|
||||||
|
this.options.bindEvents.forEach((event) => {
|
||||||
|
this.instance.on(event, (...args: any[]) => cb(event, ...args));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A server-side proxy stored on the client. The proxy ID only exists on the
|
||||||
|
* client-side version of the server proxy. The event listeners are handled by
|
||||||
|
* the client and the remaining methods are proxied to the server.
|
||||||
|
*/
|
||||||
|
export interface ClientServerProxy<T extends EventEmitter = EventEmitter> extends ServerProxy<T> {
|
||||||
|
proxyId: number | Module;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported top-level module proxies.
|
||||||
|
*/
|
||||||
|
export enum Module {
|
||||||
|
Fs = "fs",
|
||||||
|
ChildProcess = "child_process",
|
||||||
|
Net = "net",
|
||||||
|
Spdlog = "spdlog",
|
||||||
|
NodePty = "node-pty",
|
||||||
|
Trash = "trash",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BatchItem<T, A> {
|
||||||
|
args: A;
|
||||||
|
resolve: (t: T) => void;
|
||||||
|
reject: (e: Error) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch remote calls.
|
||||||
|
*/
|
||||||
|
export abstract class Batch<T, A> {
|
||||||
|
private idleTimeout: number | NodeJS.Timer | undefined;
|
||||||
|
private maxTimeout: number | NodeJS.Timer | undefined;
|
||||||
|
private batch = <BatchItem<T, A>[]>[];
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
/**
|
||||||
|
* Flush after reaching this amount of time.
|
||||||
|
*/
|
||||||
|
private readonly maxTime: number = 1000,
|
||||||
|
/**
|
||||||
|
* Flush after reaching this count.
|
||||||
|
*/
|
||||||
|
private readonly maxCount: number = 100,
|
||||||
|
/**
|
||||||
|
* Flush after not receiving more requests for this amount of time.
|
||||||
|
* This is pretty low by default so essentially we just end up batching
|
||||||
|
* requests that are all made at the same time.
|
||||||
|
*/
|
||||||
|
private readonly idleTime: number = 1,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public add = (args: A): Promise<T> => {
|
||||||
|
return new Promise((resolve, reject): void => {
|
||||||
|
this.batch.push({
|
||||||
|
args,
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
});
|
||||||
|
if (this.batch.length >= this.maxCount) {
|
||||||
|
this.flush();
|
||||||
|
} else {
|
||||||
|
clearTimeout(this.idleTimeout as any);
|
||||||
|
this.idleTimeout = setTimeout(this.flush, this.idleTime);
|
||||||
|
if (typeof this.maxTimeout === "undefined") {
|
||||||
|
this.maxTimeout = setTimeout(this.flush, this.maxTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform remote call for a batch.
|
||||||
|
*/
|
||||||
|
protected abstract remoteCall(batch: A[]): Promise<(T | Error)[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush out the current batch.
|
||||||
|
*/
|
||||||
|
private readonly flush = (): void => {
|
||||||
|
clearTimeout(this.idleTimeout as any);
|
||||||
|
clearTimeout(this.maxTimeout as any);
|
||||||
|
this.maxTimeout = undefined;
|
||||||
|
|
||||||
|
const batch = this.batch;
|
||||||
|
this.batch = [];
|
||||||
|
|
||||||
|
this.remoteCall(batch.map((q) => q.args)).then((results) => {
|
||||||
|
batch.forEach((item, i) => {
|
||||||
|
const result = results[i];
|
||||||
|
if (result && result instanceof Error) {
|
||||||
|
item.reject(result);
|
||||||
|
} else {
|
||||||
|
item.resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch((error) => batch.forEach((item) => item.reject(error)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { Argument, Module as ProtoModule, WorkingInit } from "../proto";
|
||||||
|
import { OperatingSystem } from "../common/connection";
|
||||||
|
import { ClientServerProxy, Module, ServerProxy } from "./proxy";
|
||||||
|
|
||||||
|
// tslint:disable no-any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if we're in a browser environment (including web workers).
|
* Return true if we're in a browser environment (including web workers).
|
||||||
*/
|
*/
|
||||||
@@ -13,87 +19,228 @@ export const escapePath = (path: string): string => {
|
|||||||
return `'${path.replace(/'/g, "'\\''")}'`;
|
return `'${path.replace(/'/g, "'\\''")}'`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EventCallback = (event: string, ...args: any[]) => void;
|
||||||
|
|
||||||
export type IEncodingOptions = {
|
export type IEncodingOptions = {
|
||||||
encoding?: string | null;
|
encoding?: BufferEncoding | null;
|
||||||
flag?: string;
|
flag?: string;
|
||||||
mode?: string;
|
mode?: string;
|
||||||
persistent?: boolean;
|
persistent?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
} | string | undefined | null;
|
} | BufferEncoding | undefined | null;
|
||||||
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
export type IEncodingOptionsCallback = IEncodingOptions | ((err: NodeJS.ErrnoException, ...args: any[]) => void);
|
export type IEncodingOptionsCallback = IEncodingOptions | ((err: NodeJS.ErrnoException, ...args: any[]) => void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stringify an event argument. isError is because although methods like
|
* Convert an argument to proto.
|
||||||
* `fs.stat` are supposed to throw Error objects, they currently throw regular
|
* If sending a function is possible, provide `storeFunction`.
|
||||||
* objects when running tests through Jest.
|
* If sending a proxy is possible, provide `storeProxy`.
|
||||||
*/
|
*/
|
||||||
export const stringify = (arg: any, isError?: boolean): string => { // tslint:disable-line no-any
|
export const argumentToProto = <P = ClientServerProxy | ServerProxy>(
|
||||||
if (arg instanceof Error || isError) {
|
value: any,
|
||||||
// Errors don't stringify at all. They just become "{}".
|
storeFunction?: (fn: () => void) => number,
|
||||||
return JSON.stringify({
|
storeProxy?: (proxy: P) => number | Module,
|
||||||
type: "Error",
|
): Argument => {
|
||||||
data: {
|
const convert = (currentValue: any): Argument => {
|
||||||
message: arg.message,
|
const message = new Argument();
|
||||||
stack: arg.stack,
|
|
||||||
code: (arg as NodeJS.ErrnoException).code,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (arg instanceof Uint8Array) {
|
|
||||||
// With stringify, these get turned into objects with each index becoming a
|
|
||||||
// key for some reason. Then trying to do something like write that data
|
|
||||||
// results in [object Object] being written. Stringify them like a Buffer
|
|
||||||
// instead.
|
|
||||||
return JSON.stringify({
|
|
||||||
type: "Buffer",
|
|
||||||
data: Array.from(arg),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(arg);
|
if (isProxy<P>(currentValue)) {
|
||||||
};
|
if (!storeProxy) {
|
||||||
/**
|
throw new Error("no way to serialize proxy");
|
||||||
* Parse an event argument.
|
}
|
||||||
*/
|
const arg = new Argument.ProxyValue();
|
||||||
export const parse = (arg: string): any => { // tslint:disable-line no-any
|
const id = storeProxy(currentValue);
|
||||||
const convert = (value: any): any => { // tslint:disable-line no-any
|
if (typeof id === "string") {
|
||||||
if (value && value.data && value.type) {
|
throw new Error("unable to serialize module proxy");
|
||||||
switch (value.type) {
|
}
|
||||||
// JSON.stringify turns a Buffer into an object but JSON.parse doesn't
|
arg.setId(id);
|
||||||
// turn it back, it just remains an object.
|
message.setProxy(arg);
|
||||||
case "Buffer":
|
} else if (currentValue instanceof Error
|
||||||
if (Array.isArray(value.data)) {
|
|| (currentValue && typeof currentValue.message !== "undefined"
|
||||||
return Buffer.from(value);
|
&& typeof currentValue.stack !== "undefined")) {
|
||||||
}
|
const arg = new Argument.ErrorValue();
|
||||||
|
arg.setMessage(currentValue.message);
|
||||||
|
arg.setStack(currentValue.stack);
|
||||||
|
arg.setCode(currentValue.code);
|
||||||
|
message.setError(arg);
|
||||||
|
} else if (currentValue instanceof Uint8Array || currentValue instanceof Buffer) {
|
||||||
|
const arg = new Argument.BufferValue();
|
||||||
|
arg.setData(currentValue);
|
||||||
|
message.setBuffer(arg);
|
||||||
|
} else if (Array.isArray(currentValue)) {
|
||||||
|
const arg = new Argument.ArrayValue();
|
||||||
|
arg.setDataList(currentValue.map(convert));
|
||||||
|
message.setArray(arg);
|
||||||
|
} else if (currentValue instanceof Date
|
||||||
|
|| (currentValue && typeof currentValue.getTime === "function")) {
|
||||||
|
const arg = new Argument.DateValue();
|
||||||
|
arg.setDate(currentValue.toString());
|
||||||
|
message.setDate(arg);
|
||||||
|
} else if (currentValue !== null && typeof currentValue === "object") {
|
||||||
|
const arg = new Argument.ObjectValue();
|
||||||
|
const map = arg.getDataMap();
|
||||||
|
Object.keys(currentValue).forEach((key) => {
|
||||||
|
map.set(key, convert(currentValue[key]));
|
||||||
|
});
|
||||||
|
message.setObject(arg);
|
||||||
|
} else if (currentValue === null) {
|
||||||
|
message.setNull(new Argument.NullValue());
|
||||||
|
} else {
|
||||||
|
switch (typeof currentValue) {
|
||||||
|
case "undefined":
|
||||||
|
message.setUndefined(new Argument.UndefinedValue());
|
||||||
break;
|
break;
|
||||||
// Errors apparently can't be stringified, so we do something similar to
|
case "function":
|
||||||
// what happens to buffers and stringify them as regular objects.
|
if (!storeFunction) {
|
||||||
case "Error":
|
throw new Error("no way to serialize function");
|
||||||
if (value.data.message) {
|
|
||||||
const error = new Error(value.data.message);
|
|
||||||
// TODO: Can we set the stack? Doing so seems to make it into an
|
|
||||||
// "invalid object".
|
|
||||||
if (typeof value.data.code !== "undefined") {
|
|
||||||
(error as NodeJS.ErrnoException).code = value.data.code;
|
|
||||||
}
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
(error as any).originalStack = value.data.stack;
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
}
|
||||||
|
const arg = new Argument.FunctionValue();
|
||||||
|
arg.setId(storeFunction(currentValue));
|
||||||
|
message.setFunction(arg);
|
||||||
break;
|
break;
|
||||||
|
case "number":
|
||||||
|
message.setNumber(currentValue);
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
message.setString(currentValue);
|
||||||
|
break;
|
||||||
|
case "boolean":
|
||||||
|
message.setBoolean(currentValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`cannot convert ${typeof currentValue} to proto`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value && typeof value === "object") {
|
return message;
|
||||||
Object.keys(value).forEach((key) => {
|
|
||||||
value[key] = convert(value[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return arg ? convert(JSON.parse(arg)) : arg;
|
return convert(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert proto to an argument.
|
||||||
|
* If running a remote callback is supported, provide `runCallback`.
|
||||||
|
* If using a remote proxy is supported, provide `createProxy`.
|
||||||
|
*/
|
||||||
|
export const protoToArgument = (
|
||||||
|
message?: Argument,
|
||||||
|
runCallback?: (id: number, args: any[]) => void,
|
||||||
|
createProxy?: (id: number) => ServerProxy,
|
||||||
|
): any => {
|
||||||
|
const convert = (currentMessage: Argument): any => {
|
||||||
|
switch (currentMessage.getMsgCase()) {
|
||||||
|
case Argument.MsgCase.ERROR:
|
||||||
|
const errorMessage = currentMessage.getError()!;
|
||||||
|
const error = new Error(errorMessage.getMessage());
|
||||||
|
(error as NodeJS.ErrnoException).code = errorMessage.getCode();
|
||||||
|
(error as any).originalStack = errorMessage.getStack();
|
||||||
|
|
||||||
|
return error;
|
||||||
|
case Argument.MsgCase.BUFFER:
|
||||||
|
return Buffer.from(currentMessage.getBuffer()!.getData() as Uint8Array);
|
||||||
|
case Argument.MsgCase.ARRAY:
|
||||||
|
return currentMessage.getArray()!.getDataList().map((a) => convert(a));
|
||||||
|
case Argument.MsgCase.PROXY:
|
||||||
|
if (!createProxy) {
|
||||||
|
throw new Error("no way to create proxy");
|
||||||
|
}
|
||||||
|
|
||||||
|
return createProxy(currentMessage.getProxy()!.getId());
|
||||||
|
case Argument.MsgCase.DATE:
|
||||||
|
return new Date(currentMessage.getDate()!.getDate());
|
||||||
|
case Argument.MsgCase.OBJECT:
|
||||||
|
const obj: { [Key: string]: any } = {};
|
||||||
|
currentMessage.getObject()!.getDataMap().forEach((argument, key) => {
|
||||||
|
obj[key] = convert(argument);
|
||||||
|
});
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
case Argument.MsgCase.UNDEFINED:
|
||||||
|
return undefined;
|
||||||
|
case Argument.MsgCase.NULL:
|
||||||
|
return null;
|
||||||
|
case Argument.MsgCase.FUNCTION:
|
||||||
|
if (!runCallback) {
|
||||||
|
throw new Error("no way to run remote callback");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (...args: any[]): void => {
|
||||||
|
return runCallback(currentMessage.getFunction()!.getId(), args);
|
||||||
|
};
|
||||||
|
case Argument.MsgCase.NUMBER:
|
||||||
|
return currentMessage.getNumber();
|
||||||
|
case Argument.MsgCase.STRING:
|
||||||
|
return currentMessage.getString();
|
||||||
|
case Argument.MsgCase.BOOLEAN:
|
||||||
|
return currentMessage.getBoolean();
|
||||||
|
default:
|
||||||
|
throw new Error("cannot convert unexpected proto to argument");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return message && convert(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const protoToModule = (protoModule: ProtoModule): Module => {
|
||||||
|
switch (protoModule) {
|
||||||
|
case ProtoModule.CHILDPROCESS: return Module.ChildProcess;
|
||||||
|
case ProtoModule.FS: return Module.Fs;
|
||||||
|
case ProtoModule.NET: return Module.Net;
|
||||||
|
case ProtoModule.NODEPTY: return Module.NodePty;
|
||||||
|
case ProtoModule.SPDLOG: return Module.Spdlog;
|
||||||
|
case ProtoModule.TRASH: return Module.Trash;
|
||||||
|
default: throw new Error(`invalid module ${protoModule}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const moduleToProto = (moduleName: Module): ProtoModule => {
|
||||||
|
switch (moduleName) {
|
||||||
|
case Module.ChildProcess: return ProtoModule.CHILDPROCESS;
|
||||||
|
case Module.Fs: return ProtoModule.FS;
|
||||||
|
case Module.Net: return ProtoModule.NET;
|
||||||
|
case Module.NodePty: return ProtoModule.NODEPTY;
|
||||||
|
case Module.Spdlog: return ProtoModule.SPDLOG;
|
||||||
|
case Module.Trash: return ProtoModule.TRASH;
|
||||||
|
default: throw new Error(`invalid module "${moduleName}"`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const protoToOperatingSystem = (protoOp: WorkingInit.OperatingSystem): OperatingSystem => {
|
||||||
|
switch (protoOp) {
|
||||||
|
case WorkingInit.OperatingSystem.WINDOWS: return OperatingSystem.Windows;
|
||||||
|
case WorkingInit.OperatingSystem.LINUX: return OperatingSystem.Linux;
|
||||||
|
case WorkingInit.OperatingSystem.MAC: return OperatingSystem.Mac;
|
||||||
|
default: throw new Error(`unsupported operating system ${protoOp}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const platformToProto = (platform: NodeJS.Platform): WorkingInit.OperatingSystem => {
|
||||||
|
switch (platform) {
|
||||||
|
case "win32": return WorkingInit.OperatingSystem.WINDOWS;
|
||||||
|
case "linux": return WorkingInit.OperatingSystem.LINUX;
|
||||||
|
case "darwin": return WorkingInit.OperatingSystem.MAC;
|
||||||
|
default: throw new Error(`unrecognized platform "${platform}"`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isProxy = <P = ClientServerProxy | ServerProxy>(value: any): value is P => {
|
||||||
|
return value && typeof value === "object" && typeof value.onEvent === "function";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPromise = (value: any): value is Promise<any> => {
|
||||||
|
return typeof value.then === "function" && typeof value.catch === "function";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When spawning VS Code tries to preserve the environment but since it's in
|
||||||
|
* the browser, it doesn't work.
|
||||||
|
*/
|
||||||
|
export const withEnv = <T extends { env?: NodeJS.ProcessEnv }>(options?: T): T | undefined => {
|
||||||
|
return options && options.env ? {
|
||||||
|
...options,
|
||||||
|
env: {
|
||||||
|
...process.env, ...options.env,
|
||||||
|
},
|
||||||
|
} : options;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from "./browser/client";
|
export * from "./browser/client";
|
||||||
export * from "./common/connection";
|
export * from "./common/connection";
|
||||||
export * from "./common/helpers";
|
export * from "./common/proxy";
|
||||||
export * from "./common/util";
|
export * from "./common/util";
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
import { fork as cpFork } from "child_process";
|
|
||||||
import { EventEmitter } from "events";
|
|
||||||
import * as vm from "vm";
|
|
||||||
import { logger, field } from "@coder/logger";
|
|
||||||
import { NewEvalMessage, EvalFailedMessage, EvalDoneMessage, ServerMessage, EvalEventMessage } from "../proto";
|
|
||||||
import { SendableConnection } from "../common/connection";
|
|
||||||
import { ServerActiveEvalHelper, EvalHelper, ForkProvider } from "../common/helpers";
|
|
||||||
import { stringify, parse } from "../common/util";
|
|
||||||
|
|
||||||
export interface ActiveEvaluation {
|
|
||||||
onEvent(msg: EvalEventMessage): void;
|
|
||||||
dispose(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare var __non_webpack_require__: typeof require;
|
|
||||||
export const evaluate = (connection: SendableConnection, message: NewEvalMessage, onDispose: () => void, fork?: ForkProvider): ActiveEvaluation | void => {
|
|
||||||
/**
|
|
||||||
* Send the response and call onDispose.
|
|
||||||
*/
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
const sendResp = (resp: any): void => {
|
|
||||||
logger.trace(() => [
|
|
||||||
"resolve",
|
|
||||||
field("id", message.getId()),
|
|
||||||
field("response", stringify(resp)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const evalDone = new EvalDoneMessage();
|
|
||||||
evalDone.setId(message.getId());
|
|
||||||
evalDone.setResponse(stringify(resp));
|
|
||||||
|
|
||||||
const serverMsg = new ServerMessage();
|
|
||||||
serverMsg.setEvalDone(evalDone);
|
|
||||||
connection.send(serverMsg.serializeBinary());
|
|
||||||
|
|
||||||
onDispose();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an exception and call onDispose.
|
|
||||||
*/
|
|
||||||
const sendException = (error: Error): void => {
|
|
||||||
logger.trace(() => [
|
|
||||||
"reject",
|
|
||||||
field("id", message.getId()),
|
|
||||||
field("response", stringify(error, true)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const evalFailed = new EvalFailedMessage();
|
|
||||||
evalFailed.setId(message.getId());
|
|
||||||
evalFailed.setResponse(stringify(error, true));
|
|
||||||
|
|
||||||
const serverMsg = new ServerMessage();
|
|
||||||
serverMsg.setEvalFailed(evalFailed);
|
|
||||||
connection.send(serverMsg.serializeBinary());
|
|
||||||
|
|
||||||
onDispose();
|
|
||||||
};
|
|
||||||
|
|
||||||
let eventEmitter = message.getActive() ? new EventEmitter(): undefined;
|
|
||||||
const sandbox = {
|
|
||||||
helper: eventEmitter ? new ServerActiveEvalHelper({
|
|
||||||
removeAllListeners: (event?: string): void => {
|
|
||||||
eventEmitter!.removeAllListeners(event);
|
|
||||||
},
|
|
||||||
// tslint:disable no-any
|
|
||||||
on: (event: string, cb: (...args: any[]) => void): void => {
|
|
||||||
eventEmitter!.on(event, (...args: any[]) => {
|
|
||||||
logger.trace(() => [
|
|
||||||
`${event}`,
|
|
||||||
field("id", message.getId()),
|
|
||||||
field("args", args.map((a) => stringify(a))),
|
|
||||||
]);
|
|
||||||
cb(...args);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
emit: (event: string, ...args: any[]): void => {
|
|
||||||
logger.trace(() => [
|
|
||||||
`emit ${event}`,
|
|
||||||
field("id", message.getId()),
|
|
||||||
field("args", args.map((a) => stringify(a))),
|
|
||||||
]);
|
|
||||||
const eventMsg = new EvalEventMessage();
|
|
||||||
eventMsg.setEvent(event);
|
|
||||||
eventMsg.setArgsList(args.map((a) => stringify(a)));
|
|
||||||
eventMsg.setId(message.getId());
|
|
||||||
const serverMsg = new ServerMessage();
|
|
||||||
serverMsg.setEvalEvent(eventMsg);
|
|
||||||
connection.send(serverMsg.serializeBinary());
|
|
||||||
},
|
|
||||||
// tslint:enable no-any
|
|
||||||
}, fork || cpFork) : new EvalHelper(),
|
|
||||||
_Buffer: Buffer,
|
|
||||||
// When the client is ran from Webpack, it will replace
|
|
||||||
// __non_webpack_require__ with require, which we then need to provide to
|
|
||||||
// the sandbox. Since the server might also be using Webpack, we need to set
|
|
||||||
// it to the non-Webpack version when that's the case. Then we need to also
|
|
||||||
// provide __non_webpack_require__ for when the client doesn't run through
|
|
||||||
// Webpack meaning it doesn't get replaced with require (Jest for example).
|
|
||||||
require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
|
|
||||||
__non_webpack_require__: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
|
|
||||||
setTimeout,
|
|
||||||
setInterval,
|
|
||||||
clearTimeout,
|
|
||||||
process: {
|
|
||||||
env: process.env,
|
|
||||||
},
|
|
||||||
args: message.getArgsList().map(parse),
|
|
||||||
};
|
|
||||||
|
|
||||||
let value: any; // tslint:disable-line no-any
|
|
||||||
try {
|
|
||||||
const code = `(${message.getFunction()})(helper, ...args);`;
|
|
||||||
value = vm.runInNewContext(code, sandbox, {
|
|
||||||
// If the code takes longer than this to return, it is killed and throws.
|
|
||||||
timeout: message.getTimeout() || 15000,
|
|
||||||
});
|
|
||||||
} catch (ex) {
|
|
||||||
sendException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// An evaluation completes when the value it returns resolves. An active
|
|
||||||
// evaluation completes when it is disposed. Active evaluations are required
|
|
||||||
// to return disposers so we can know both when it has ended (so we can clean
|
|
||||||
// up on our end) and how to force end it (for example when the client
|
|
||||||
// disconnects).
|
|
||||||
// tslint:disable-next-line no-any
|
|
||||||
const promise = !eventEmitter ? value as Promise<any> : new Promise((resolve): void => {
|
|
||||||
value.onDidDispose(resolve);
|
|
||||||
});
|
|
||||||
if (promise && promise.then) {
|
|
||||||
promise.then(sendResp).catch(sendException);
|
|
||||||
} else {
|
|
||||||
sendResp(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return eventEmitter ? {
|
|
||||||
onEvent: (eventMsg: EvalEventMessage): void => {
|
|
||||||
eventEmitter!.emit(eventMsg.getEvent(), ...eventMsg.getArgsList().map(parse));
|
|
||||||
},
|
|
||||||
dispose: (): void => {
|
|
||||||
if (eventEmitter) {
|
|
||||||
if (value && value.dispose) {
|
|
||||||
value.dispose();
|
|
||||||
}
|
|
||||||
eventEmitter.removeAllListeners();
|
|
||||||
eventEmitter = undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} : undefined;
|
|
||||||
};
|
|
||||||
95
packages/protocol/src/node/modules/child_process.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import * as cp from "child_process";
|
||||||
|
import { ServerProxy } from "../../common/proxy";
|
||||||
|
import { withEnv } from "../../common/util";
|
||||||
|
import { WritableProxy, ReadableProxy } from "./stream";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
export type ForkProvider = (modulePath: string, args?: string[], options?: cp.ForkOptions) => cp.ChildProcess;
|
||||||
|
|
||||||
|
export class ChildProcessProxy extends ServerProxy<cp.ChildProcess> {
|
||||||
|
public constructor(instance: cp.ChildProcess) {
|
||||||
|
super({
|
||||||
|
bindEvents: ["close", "disconnect", "error", "exit", "message"],
|
||||||
|
doneEvents: ["close"],
|
||||||
|
instance,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async kill(signal?: string): Promise<void> {
|
||||||
|
this.instance.kill(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async disconnect(): Promise<void> {
|
||||||
|
this.instance.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ref(): Promise<void> {
|
||||||
|
this.instance.ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async unref(): Promise<void> {
|
||||||
|
this.instance.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line no-any
|
||||||
|
public async send(message: any): Promise<void> {
|
||||||
|
return new Promise((resolve, reject): void => {
|
||||||
|
this.instance.send(message, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getPid(): Promise<number> {
|
||||||
|
return this.instance.pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.kill();
|
||||||
|
setTimeout(() => this.instance.kill("SIGKILL"), 5000); // Double tap.
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChildProcessProxies {
|
||||||
|
childProcess: ChildProcessProxy;
|
||||||
|
stdin?: WritableProxy | null;
|
||||||
|
stdout?: ReadableProxy | null;
|
||||||
|
stderr?: ReadableProxy | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChildProcessModuleProxy {
|
||||||
|
public constructor(private readonly forkProvider?: ForkProvider) {}
|
||||||
|
|
||||||
|
public async exec(
|
||||||
|
command: string,
|
||||||
|
options?: { encoding?: string | null } & cp.ExecOptions | null,
|
||||||
|
callback?: ((error: cp.ExecException | null, stdin: string | Buffer, stdout: string | Buffer) => void),
|
||||||
|
): Promise<ChildProcessProxies> {
|
||||||
|
return this.returnProxies(cp.exec(command, options && withEnv(options), callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fork(modulePath: string, args?: string[], options?: cp.ForkOptions): Promise<ChildProcessProxies> {
|
||||||
|
return this.returnProxies((this.forkProvider || cp.fork)(modulePath, args, withEnv(options)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async spawn(command: string, args?: string[], options?: cp.SpawnOptions): Promise<ChildProcessProxies> {
|
||||||
|
return this.returnProxies(cp.spawn(command, args, withEnv(options)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private returnProxies(process: cp.ChildProcess): ChildProcessProxies {
|
||||||
|
return {
|
||||||
|
childProcess: new ChildProcessProxy(process),
|
||||||
|
stdin: process.stdin && new WritableProxy(process.stdin),
|
||||||
|
// Child processes streams appear to immediately flow so we need to bind
|
||||||
|
// to the data event right away.
|
||||||
|
stdout: process.stdout && new ReadableProxy(process.stdout, ["data"]),
|
||||||
|
stderr: process.stderr && new ReadableProxy(process.stderr, ["data"]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
272
packages/protocol/src/node/modules/fs.ts
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { promisify } from "util";
|
||||||
|
import { ServerProxy } from "../../common/proxy";
|
||||||
|
import { IEncodingOptions } from "../../common/util";
|
||||||
|
import { ReadableProxy, WritableProxy } from "./stream";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs no-any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A serializable version of fs.Stats.
|
||||||
|
*/
|
||||||
|
export interface Stats {
|
||||||
|
dev: number;
|
||||||
|
ino: number;
|
||||||
|
mode: number;
|
||||||
|
nlink: number;
|
||||||
|
uid: number;
|
||||||
|
gid: number;
|
||||||
|
rdev: number;
|
||||||
|
size: number;
|
||||||
|
blksize: number;
|
||||||
|
blocks: number;
|
||||||
|
atimeMs: number;
|
||||||
|
mtimeMs: number;
|
||||||
|
ctimeMs: number;
|
||||||
|
birthtimeMs: number;
|
||||||
|
atime: Date;
|
||||||
|
mtime: Date;
|
||||||
|
ctime: Date;
|
||||||
|
birthtime: Date;
|
||||||
|
_isFile: boolean;
|
||||||
|
_isDirectory: boolean;
|
||||||
|
_isBlockDevice: boolean;
|
||||||
|
_isCharacterDevice: boolean;
|
||||||
|
_isSymbolicLink: boolean;
|
||||||
|
_isFIFO: boolean;
|
||||||
|
_isSocket: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReadStreamProxy extends ReadableProxy<fs.ReadStream> {
|
||||||
|
public constructor(stream: fs.ReadStream) {
|
||||||
|
super(stream, ["open"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
this.instance.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.close();
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WriteStreamProxy extends WritableProxy<fs.WriteStream> {
|
||||||
|
public constructor(stream: fs.WriteStream) {
|
||||||
|
super(stream, ["open"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
this.instance.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.close();
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WatcherProxy extends ServerProxy<fs.FSWatcher> {
|
||||||
|
public constructor(watcher: fs.FSWatcher) {
|
||||||
|
super({
|
||||||
|
bindEvents: ["change", "close", "error"],
|
||||||
|
doneEvents: ["close", "error"],
|
||||||
|
instance: watcher,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
this.instance.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.close();
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FsModuleProxy {
|
||||||
|
public access(path: fs.PathLike, mode?: number): Promise<void> {
|
||||||
|
return promisify(fs.access)(path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public appendFile(file: fs.PathLike | number, data: any, options?: fs.WriteFileOptions): Promise<void> {
|
||||||
|
return promisify(fs.appendFile)(file, data, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public chmod(path: fs.PathLike, mode: string | number): Promise<void> {
|
||||||
|
return promisify(fs.chmod)(path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public chown(path: fs.PathLike, uid: number, gid: number): Promise<void> {
|
||||||
|
return promisify(fs.chown)(path, uid, gid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(fd: number): Promise<void> {
|
||||||
|
return promisify(fs.close)(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public copyFile(src: fs.PathLike, dest: fs.PathLike, flags?: number): Promise<void> {
|
||||||
|
return promisify(fs.copyFile)(src, dest, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createReadStream(path: fs.PathLike, options?: any): Promise<ReadStreamProxy> {
|
||||||
|
return new ReadStreamProxy(fs.createReadStream(path, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createWriteStream(path: fs.PathLike, options?: any): Promise<WriteStreamProxy> {
|
||||||
|
return new WriteStreamProxy(fs.createWriteStream(path, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public exists(path: fs.PathLike): Promise<boolean> {
|
||||||
|
return promisify(fs.exists)(path); // tslint:disable-line deprecation
|
||||||
|
}
|
||||||
|
|
||||||
|
public fchmod(fd: number, mode: string | number): Promise<void> {
|
||||||
|
return promisify(fs.fchmod)(fd, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fchown(fd: number, uid: number, gid: number): Promise<void> {
|
||||||
|
return promisify(fs.fchown)(fd, uid, gid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fdatasync(fd: number): Promise<void> {
|
||||||
|
return promisify(fs.fdatasync)(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fstat(fd: number): Promise<Stats> {
|
||||||
|
return this.makeStatsSerializable(await promisify(fs.fstat)(fd));
|
||||||
|
}
|
||||||
|
|
||||||
|
public fsync(fd: number): Promise<void> {
|
||||||
|
return promisify(fs.fsync)(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ftruncate(fd: number, len?: number | null): Promise<void> {
|
||||||
|
return promisify(fs.ftruncate)(fd, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public futimes(fd: number, atime: string | number | Date, mtime: string | number | Date): Promise<void> {
|
||||||
|
return promisify(fs.futimes)(fd, atime, mtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public lchmod(path: fs.PathLike, mode: string | number): Promise<void> {
|
||||||
|
return promisify(fs.lchmod)(path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public lchown(path: fs.PathLike, uid: number, gid: number): Promise<void> {
|
||||||
|
return promisify(fs.lchown)(path, uid, gid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public link(existingPath: fs.PathLike, newPath: fs.PathLike): Promise<void> {
|
||||||
|
return promisify(fs.link)(existingPath, newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async lstat(path: fs.PathLike): Promise<Stats> {
|
||||||
|
return this.makeStatsSerializable(await promisify(fs.lstat)(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async lstatBatch(args: { path: fs.PathLike }[]): Promise<(Stats | Error)[]> {
|
||||||
|
return Promise.all(args.map((a) => this.lstat(a.path).catch((e) => e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public mkdir(path: fs.PathLike, mode: number | string | fs.MakeDirectoryOptions | undefined | null): Promise<void> {
|
||||||
|
return promisify(fs.mkdir)(path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mkdtemp(prefix: string, options: IEncodingOptions): Promise<string | Buffer> {
|
||||||
|
return promisify(fs.mkdtemp)(prefix, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public open(path: fs.PathLike, flags: string | number, mode: string | number | undefined | null): Promise<number> {
|
||||||
|
return promisify(fs.open)(path, flags, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public read(fd: number, length: number, position: number | null): Promise<{ bytesRead: number, buffer: Buffer }> {
|
||||||
|
const buffer = Buffer.alloc(length);
|
||||||
|
|
||||||
|
return promisify(fs.read)(fd, buffer, 0, length, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readFile(path: fs.PathLike | number, options: IEncodingOptions): Promise<string | Buffer> {
|
||||||
|
return promisify(fs.readFile)(path, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readdir(path: fs.PathLike, options: IEncodingOptions): Promise<Buffer[] | fs.Dirent[] | string[]> {
|
||||||
|
return promisify(fs.readdir)(path, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readdirBatch(args: { path: fs.PathLike, options: IEncodingOptions }[]): Promise<(Buffer[] | fs.Dirent[] | string[] | Error)[]> {
|
||||||
|
return Promise.all(args.map((a) => this.readdir(a.path, a.options).catch((e) => e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public readlink(path: fs.PathLike, options: IEncodingOptions): Promise<string | Buffer> {
|
||||||
|
return promisify(fs.readlink)(path, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public realpath(path: fs.PathLike, options: IEncodingOptions): Promise<string | Buffer> {
|
||||||
|
return promisify(fs.realpath)(path, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public rename(oldPath: fs.PathLike, newPath: fs.PathLike): Promise<void> {
|
||||||
|
return promisify(fs.rename)(oldPath, newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public rmdir(path: fs.PathLike): Promise<void> {
|
||||||
|
return promisify(fs.rmdir)(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stat(path: fs.PathLike): Promise<Stats> {
|
||||||
|
return this.makeStatsSerializable(await promisify(fs.stat)(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async statBatch(args: { path: fs.PathLike }[]): Promise<(Stats | Error)[]> {
|
||||||
|
return Promise.all(args.map((a) => this.stat(a.path).catch((e) => e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public symlink(target: fs.PathLike, path: fs.PathLike, type?: fs.symlink.Type | null): Promise<void> {
|
||||||
|
return promisify(fs.symlink)(target, path, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public truncate(path: fs.PathLike, len?: number | null): Promise<void> {
|
||||||
|
return promisify(fs.truncate)(path, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unlink(path: fs.PathLike): Promise<void> {
|
||||||
|
return promisify(fs.unlink)(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public utimes(path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date): Promise<void> {
|
||||||
|
return promisify(fs.utimes)(path, atime, mtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(fd: number, buffer: Buffer, offset?: number, length?: number, position?: number): Promise<{ bytesWritten: number, buffer: Buffer }> {
|
||||||
|
return promisify(fs.write)(fd, buffer, offset, length, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeFile (path: fs.PathLike | number, data: any, options: IEncodingOptions): Promise<void> {
|
||||||
|
return promisify(fs.writeFile)(path, data, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async watch(filename: fs.PathLike, options?: IEncodingOptions): Promise<WatcherProxy> {
|
||||||
|
return new WatcherProxy(fs.watch(filename, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeStatsSerializable(stats: fs.Stats): Stats {
|
||||||
|
return {
|
||||||
|
...stats,
|
||||||
|
/**
|
||||||
|
* We need to check if functions exist because nexe's implemented FS
|
||||||
|
* lib doesnt implement fs.stats properly.
|
||||||
|
*/
|
||||||
|
_isBlockDevice: stats.isBlockDevice ? stats.isBlockDevice() : false,
|
||||||
|
_isCharacterDevice: stats.isCharacterDevice ? stats.isCharacterDevice() : false,
|
||||||
|
_isDirectory: stats.isDirectory(),
|
||||||
|
_isFIFO: stats.isFIFO ? stats.isFIFO() : false,
|
||||||
|
_isFile: stats.isFile(),
|
||||||
|
_isSocket: stats.isSocket ? stats.isSocket() : false,
|
||||||
|
_isSymbolicLink: stats.isSymbolicLink ? stats.isSymbolicLink() : false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/protocol/src/node/modules/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from "./child_process";
|
||||||
|
export * from "./fs";
|
||||||
|
export * from "./net";
|
||||||
|
export * from "./node-pty";
|
||||||
|
export * from "./spdlog";
|
||||||
|
export * from "./trash";
|
||||||
79
packages/protocol/src/node/modules/net.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import * as net from "net";
|
||||||
|
import { ServerProxy } from "../../common/proxy";
|
||||||
|
import { DuplexProxy } from "./stream";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs no-any
|
||||||
|
|
||||||
|
export class NetSocketProxy extends DuplexProxy<net.Socket> {
|
||||||
|
public constructor(socket: net.Socket) {
|
||||||
|
super(socket, ["connect", "lookup", "timeout"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connect(options: number | string | net.SocketConnectOpts, host?: string): Promise<void> {
|
||||||
|
this.instance.connect(options as any, host as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async unref(): Promise<void> {
|
||||||
|
this.instance.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ref(): Promise<void> {
|
||||||
|
this.instance.ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.end();
|
||||||
|
this.instance.destroy();
|
||||||
|
this.instance.unref();
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NetServerProxy extends ServerProxy<net.Server> {
|
||||||
|
public constructor(instance: net.Server) {
|
||||||
|
super({
|
||||||
|
bindEvents: ["close", "error", "listening"],
|
||||||
|
doneEvents: ["close"],
|
||||||
|
instance,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listen(handle?: net.ListenOptions | number | string, hostname?: string | number, backlog?: number): Promise<void> {
|
||||||
|
this.instance.listen(handle, hostname as any, backlog as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ref(): Promise<void> {
|
||||||
|
this.instance.ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async unref(): Promise<void> {
|
||||||
|
this.instance.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
this.instance.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onConnection(cb: (proxy: NetSocketProxy) => void): Promise<void> {
|
||||||
|
this.instance.on("connection", (socket) => cb(new NetSocketProxy(socket)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.close();
|
||||||
|
this.instance.removeAllListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NetModuleProxy {
|
||||||
|
public async createSocket(options?: net.SocketConstructorOpts): Promise<NetSocketProxy> {
|
||||||
|
return new NetSocketProxy(new net.Socket(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createConnection(target: string | number | net.NetConnectOpts, host?: string): Promise<NetSocketProxy> {
|
||||||
|
return new NetSocketProxy(net.createConnection(target as any, host));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createServer(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean }): Promise<NetServerProxy> {
|
||||||
|
return new NetServerProxy(net.createServer(options));
|
||||||
|
}
|
||||||
|
}
|
||||||
71
packages/protocol/src/node/modules/node-pty.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/// <reference path="../../../../../lib/vscode/src/typings/node-pty.d.ts" />
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import * as pty from "node-pty";
|
||||||
|
import { ServerProxy } from "../../common/proxy";
|
||||||
|
import { withEnv } from "../../common/util";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server-side IPty proxy.
|
||||||
|
*/
|
||||||
|
export class NodePtyProcessProxy extends ServerProxy {
|
||||||
|
public constructor(private readonly process: pty.IPty) {
|
||||||
|
super({
|
||||||
|
bindEvents: ["process", "data", "exit"],
|
||||||
|
doneEvents: ["exit"],
|
||||||
|
instance: new EventEmitter(),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.process.on("data", (data) => this.instance.emit("data", data));
|
||||||
|
this.process.on("exit", (exitCode, signal) => this.instance.emit("exit", exitCode, signal));
|
||||||
|
|
||||||
|
let name = process.process;
|
||||||
|
setTimeout(() => { // Need to wait for the caller to listen to the event.
|
||||||
|
this.instance.emit("process", name);
|
||||||
|
}, 1);
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (process.process !== name) {
|
||||||
|
name = process.process;
|
||||||
|
this.instance.emit("process", name);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
this.process.on("exit", () => clearInterval(timer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getPid(): Promise<number> {
|
||||||
|
return this.process.pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getProcess(): Promise<string> {
|
||||||
|
return this.process.process;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async kill(signal?: string): Promise<void> {
|
||||||
|
this.process.kill(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resize(columns: number, rows: number): Promise<void> {
|
||||||
|
this.process.resize(columns, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(data: string): Promise<void> {
|
||||||
|
this.process.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.process.kill();
|
||||||
|
setTimeout(() => this.process.kill("SIGKILL"), 5000); // Double tap.
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server-side node-pty proxy.
|
||||||
|
*/
|
||||||
|
export class NodePtyModuleProxy {
|
||||||
|
public async spawn(file: string, args: string[] | string, options: pty.IPtyForkOptions): Promise<NodePtyProcessProxy> {
|
||||||
|
return new NodePtyProcessProxy(require("node-pty").spawn(file, args, withEnv(options)));
|
||||||
|
}
|
||||||
|
}
|
||||||
43
packages/protocol/src/node/modules/spdlog.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/// <reference path="../../../../../lib/vscode/src/typings/spdlog.d.ts" />
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import * as spdlog from "spdlog";
|
||||||
|
import { ServerProxy } from "../../common/proxy";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
export class RotatingLoggerProxy extends ServerProxy<EventEmitter> {
|
||||||
|
public constructor(private readonly logger: spdlog.RotatingLogger) {
|
||||||
|
super({
|
||||||
|
bindEvents: [],
|
||||||
|
doneEvents: ["dispose"],
|
||||||
|
instance: new EventEmitter(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async trace (message: string): Promise<void> { this.logger.trace(message); }
|
||||||
|
public async debug (message: string): Promise<void> { this.logger.debug(message); }
|
||||||
|
public async info (message: string): Promise<void> { this.logger.info(message); }
|
||||||
|
public async warn (message: string): Promise<void> { this.logger.warn(message); }
|
||||||
|
public async error (message: string): Promise<void> { this.logger.error(message); }
|
||||||
|
public async critical (message: string): Promise<void> { this.logger.critical(message); }
|
||||||
|
public async setLevel (level: number): Promise<void> { this.logger.setLevel(level); }
|
||||||
|
public async clearFormatters (): Promise<void> { this.logger.clearFormatters(); }
|
||||||
|
public async flush (): Promise<void> { this.logger.flush(); }
|
||||||
|
public async drop (): Promise<void> { this.logger.drop(); }
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
await this.flush();
|
||||||
|
this.instance.emit("dispose");
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SpdlogModuleProxy {
|
||||||
|
public async createLogger(name: string, filePath: string, fileSize: number, fileCount: number): Promise<RotatingLoggerProxy> {
|
||||||
|
return new RotatingLoggerProxy(new (require("spdlog") as typeof import("spdlog")).RotatingLogger(name, filePath, fileSize, fileCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setAsyncMode(bufferSize: number, flushInterval: number): Promise<void> {
|
||||||
|
require("spdlog").setAsyncMode(bufferSize, flushInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
packages/protocol/src/node/modules/stream.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { EventEmitter } from "events";
|
||||||
|
import * as stream from "stream";
|
||||||
|
import { ServerProxy } from "../../common/proxy";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs no-any
|
||||||
|
|
||||||
|
export class WritableProxy<T extends stream.Writable = stream.Writable> extends ServerProxy<T> {
|
||||||
|
public constructor(instance: T, bindEvents: string[] = [], delayedEvents?: string[]) {
|
||||||
|
super({
|
||||||
|
bindEvents: ["close", "drain", "error", "finish"].concat(bindEvents),
|
||||||
|
doneEvents: ["close"],
|
||||||
|
delayedEvents,
|
||||||
|
instance,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async destroy(): Promise<void> {
|
||||||
|
this.instance.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async end(data?: any, encoding?: string): Promise<void> {
|
||||||
|
return new Promise((resolve): void => {
|
||||||
|
this.instance.end(data, encoding, () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setDefaultEncoding(encoding: string): Promise<void> {
|
||||||
|
this.instance.setDefaultEncoding(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(data: any, encoding?: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject): void => {
|
||||||
|
this.instance.write(data, encoding, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.end();
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This noise is because we can't do multiple extends and we also can't seem to
|
||||||
|
* do `extends WritableProxy<T> implement ReadableProxy<T>` (for `DuplexProxy`).
|
||||||
|
*/
|
||||||
|
export interface IReadableProxy<T extends EventEmitter> extends ServerProxy<T> {
|
||||||
|
pipe<P extends WritableProxy>(destination: P, options?: { end?: boolean; }): Promise<void>;
|
||||||
|
setEncoding(encoding: string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReadableProxy<T extends stream.Readable = stream.Readable> extends ServerProxy<T> implements IReadableProxy<T> {
|
||||||
|
public constructor(instance: T, bindEvents: string[] = []) {
|
||||||
|
super({
|
||||||
|
bindEvents: ["close", "end", "error"].concat(bindEvents),
|
||||||
|
doneEvents: ["close"],
|
||||||
|
delayedEvents: ["data"],
|
||||||
|
instance,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async pipe<P extends WritableProxy>(destination: P, options?: { end?: boolean; }): Promise<void> {
|
||||||
|
this.instance.pipe(destination.instance, options);
|
||||||
|
// `pipe` switches the stream to flowing mode and makes data start emitting.
|
||||||
|
await this.bindDelayedEvent("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async destroy(): Promise<void> {
|
||||||
|
this.instance.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setEncoding(encoding: string): Promise<void> {
|
||||||
|
this.instance.setEncoding(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.destroy();
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DuplexProxy<T extends stream.Duplex = stream.Duplex> extends WritableProxy<T> implements IReadableProxy<T> {
|
||||||
|
public constructor(stream: T, bindEvents: string[] = []) {
|
||||||
|
super(stream, ["end"].concat(bindEvents), ["data"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async pipe<P extends WritableProxy>(destination: P, options?: { end?: boolean; }): Promise<void> {
|
||||||
|
this.instance.pipe(destination.instance, options);
|
||||||
|
// `pipe` switches the stream to flowing mode and makes data start emitting.
|
||||||
|
await this.bindDelayedEvent("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setEncoding(encoding: string): Promise<void> {
|
||||||
|
this.instance.setEncoding(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
this.instance.destroy();
|
||||||
|
await super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
packages/protocol/src/node/modules/trash.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import * as trash from "trash";
|
||||||
|
|
||||||
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
|
export class TrashModuleProxy {
|
||||||
|
public async trash(path: string, options?: trash.Options): Promise<void> {
|
||||||
|
return trash(path, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,123 +1,364 @@
|
|||||||
|
import { mkdirp } from "fs-extra";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
import * as path from "path";
|
import { field, logger} from "@coder/logger";
|
||||||
import { mkdir } from "fs";
|
|
||||||
import { promisify } from "util";
|
|
||||||
import { logger, field } from "@coder/logger";
|
|
||||||
import { ClientMessage, WorkingInitMessage, ServerMessage } from "../proto";
|
|
||||||
import { evaluate, ActiveEvaluation } from "./evaluate";
|
|
||||||
import { ForkProvider } from "../common/helpers";
|
|
||||||
import { ReadWriteConnection } from "../common/connection";
|
import { ReadWriteConnection } from "../common/connection";
|
||||||
|
import { Module, ServerProxy } from "../common/proxy";
|
||||||
|
import { isPromise, isProxy, moduleToProto, protoToArgument, platformToProto, protoToModule, argumentToProto } from "../common/util";
|
||||||
|
import { Argument, Callback, ClientMessage, Event, Method, Pong, ServerMessage, WorkingInit } from "../proto";
|
||||||
|
import { ChildProcessModuleProxy, ForkProvider, FsModuleProxy, NetModuleProxy, NodePtyModuleProxy, SpdlogModuleProxy, TrashModuleProxy } from "./modules";
|
||||||
|
|
||||||
|
// tslint:disable no-any
|
||||||
|
|
||||||
export interface ServerOptions {
|
export interface ServerOptions {
|
||||||
readonly workingDirectory: string;
|
readonly workingDirectory: string;
|
||||||
readonly dataDirectory: string;
|
readonly dataDirectory: string;
|
||||||
|
readonly cacheDirectory: string;
|
||||||
readonly builtInExtensionsDirectory: string;
|
readonly builtInExtensionsDirectory: string;
|
||||||
|
readonly extensionsDirectory: string;
|
||||||
|
readonly extraExtensionDirectories?: string[];
|
||||||
|
readonly extraBuiltinExtensionDirectories?: string[];
|
||||||
readonly fork?: ForkProvider;
|
readonly fork?: ForkProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProxyData {
|
||||||
|
disposeTimeout?: number | NodeJS.Timer;
|
||||||
|
instance: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle messages from the client.
|
||||||
|
*/
|
||||||
export class Server {
|
export class Server {
|
||||||
private readonly evals = new Map<number, ActiveEvaluation>();
|
private proxyId = 0;
|
||||||
|
private readonly proxies = new Map<number | Module, ProxyData>();
|
||||||
|
private disconnected: boolean = false;
|
||||||
|
private readonly responseTimeout = 10000;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly connection: ReadWriteConnection,
|
private readonly connection: ReadWriteConnection,
|
||||||
private readonly options?: ServerOptions,
|
private readonly options?: ServerOptions,
|
||||||
) {
|
) {
|
||||||
connection.onMessage((data) => {
|
connection.onMessage(async (data) => {
|
||||||
try {
|
try {
|
||||||
this.handleMessage(ClientMessage.deserializeBinary(data));
|
await this.handleMessage(ClientMessage.deserializeBinary(data));
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
logger.error("Failed to handle client message", field("length", data.byteLength), field("exception", {
|
logger.error(
|
||||||
message: ex.message,
|
"Failed to handle client message",
|
||||||
stack: ex.stack,
|
field("length", data.byteLength),
|
||||||
}));
|
field("exception", {
|
||||||
|
message: ex.message,
|
||||||
|
stack: ex.stack,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.onClose(() => {
|
connection.onClose(() => {
|
||||||
this.evals.forEach((e) => e.dispose());
|
this.disconnected = true;
|
||||||
|
|
||||||
|
logger.trace(() => [
|
||||||
|
"disconnected from client",
|
||||||
|
field("proxies", this.proxies.size),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.proxies.forEach((proxy, proxyId) => {
|
||||||
|
if (isProxy(proxy.instance)) {
|
||||||
|
proxy.instance.dispose().catch((error) => {
|
||||||
|
logger.error(error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.removeProxy(proxyId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.storeProxy(new ChildProcessModuleProxy(this.options ? this.options.fork : undefined), Module.ChildProcess);
|
||||||
|
this.storeProxy(new FsModuleProxy(), Module.Fs);
|
||||||
|
this.storeProxy(new NetModuleProxy(), Module.Net);
|
||||||
|
this.storeProxy(new NodePtyModuleProxy(), Module.NodePty);
|
||||||
|
this.storeProxy(new SpdlogModuleProxy(), Module.Spdlog);
|
||||||
|
this.storeProxy(new TrashModuleProxy(), Module.Trash);
|
||||||
|
|
||||||
if (!this.options) {
|
if (!this.options) {
|
||||||
logger.warn("No server options provided. InitMessage will not be sent.");
|
logger.warn("No server options provided. InitMessage will not be sent.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the data directory exists.
|
Promise.all([
|
||||||
const mkdirP = async (path: string): Promise<void> => {
|
mkdirp(this.options.cacheDirectory),
|
||||||
const split = path.replace(/^\/*|\/*$/g, "").split("/");
|
mkdirp(this.options.dataDirectory),
|
||||||
let dir = "";
|
mkdirp(this.options.workingDirectory),
|
||||||
while (split.length > 0) {
|
]).catch((error) => {
|
||||||
dir += "/" + split.shift();
|
|
||||||
try {
|
|
||||||
await promisify(mkdir)(dir);
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "EEXIST" && error.code !== "EISDIR") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Promise.all([ mkdirP(path.join(this.options.dataDirectory, "User", "workspaceStorage")) ]).then(() => {
|
|
||||||
logger.info("Created data directory");
|
|
||||||
}).catch((error) => {
|
|
||||||
logger.error(error.message, field("error", error));
|
logger.error(error.message, field("error", error));
|
||||||
});
|
});
|
||||||
|
|
||||||
const initMsg = new WorkingInitMessage();
|
const initMsg = new WorkingInit();
|
||||||
initMsg.setDataDirectory(this.options.dataDirectory);
|
initMsg.setDataDirectory(this.options.dataDirectory);
|
||||||
initMsg.setWorkingDirectory(this.options.workingDirectory);
|
initMsg.setWorkingDirectory(this.options.workingDirectory);
|
||||||
initMsg.setBuiltinExtensionsDir(this.options.builtInExtensionsDirectory);
|
initMsg.setBuiltinExtensionsDir(this.options.builtInExtensionsDirectory);
|
||||||
|
initMsg.setExtensionsDirectory(this.options.extensionsDirectory);
|
||||||
initMsg.setHomeDirectory(os.homedir());
|
initMsg.setHomeDirectory(os.homedir());
|
||||||
initMsg.setTmpDirectory(os.tmpdir());
|
initMsg.setTmpDirectory(os.tmpdir());
|
||||||
const platform = os.platform();
|
initMsg.setOperatingSystem(platformToProto(os.platform()));
|
||||||
let operatingSystem: WorkingInitMessage.OperatingSystem;
|
initMsg.setShell(os.userInfo().shell || global.process.env.SHELL || "");
|
||||||
switch (platform) {
|
initMsg.setExtraExtensionDirectoriesList(this.options.extraExtensionDirectories || []);
|
||||||
case "win32":
|
initMsg.setExtraBuiltinExtensionDirectoriesList(this.options.extraBuiltinExtensionDirectories || []);
|
||||||
operatingSystem = WorkingInitMessage.OperatingSystem.WINDOWS;
|
|
||||||
break;
|
|
||||||
case "linux":
|
|
||||||
operatingSystem = WorkingInitMessage.OperatingSystem.LINUX;
|
|
||||||
break;
|
|
||||||
case "darwin":
|
|
||||||
operatingSystem = WorkingInitMessage.OperatingSystem.MAC;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`unrecognized platform "${platform}"`);
|
|
||||||
}
|
|
||||||
initMsg.setOperatingSystem(operatingSystem);
|
|
||||||
initMsg.setShell(os.userInfo().shell || global.process.env.SHELL);
|
|
||||||
const srvMsg = new ServerMessage();
|
const srvMsg = new ServerMessage();
|
||||||
srvMsg.setInit(initMsg);
|
srvMsg.setInit(initMsg);
|
||||||
connection.send(srvMsg.serializeBinary());
|
connection.send(srvMsg.serializeBinary());
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleMessage(message: ClientMessage): void {
|
/**
|
||||||
if (message.hasNewEval()) {
|
* Handle all messages from the client.
|
||||||
const evalMessage = message.getNewEval()!;
|
*/
|
||||||
logger.trace(() => [
|
private async handleMessage(message: ClientMessage): Promise<void> {
|
||||||
"EvalMessage",
|
switch (message.getMsgCase()) {
|
||||||
field("id", evalMessage.getId()),
|
case ClientMessage.MsgCase.METHOD:
|
||||||
field("args", evalMessage.getArgsList()),
|
await this.runMethod(message.getMethod()!);
|
||||||
field("function", evalMessage.getFunction()),
|
break;
|
||||||
]);
|
case ClientMessage.MsgCase.PING:
|
||||||
const resp = evaluate(this.connection, evalMessage, () => {
|
logger.trace("ping");
|
||||||
this.evals.delete(evalMessage.getId());
|
const srvMsg = new ServerMessage();
|
||||||
logger.trace(() => [
|
srvMsg.setPong(new Pong());
|
||||||
`dispose ${evalMessage.getId()}, ${this.evals.size} left`,
|
this.connection.send(srvMsg.serializeBinary());
|
||||||
]);
|
break;
|
||||||
}, this.options ? this.options.fork : undefined);
|
default:
|
||||||
if (resp) {
|
throw new Error("unknown message type");
|
||||||
this.evals.set(evalMessage.getId(), resp);
|
|
||||||
}
|
|
||||||
} else if (message.hasEvalEvent()) {
|
|
||||||
const evalEventMessage = message.getEvalEvent()!;
|
|
||||||
const e = this.evals.get(evalEventMessage.getId());
|
|
||||||
if (!e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.onEvent(evalEventMessage);
|
|
||||||
} else {
|
|
||||||
throw new Error("unknown message type");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a method on a proxy.
|
||||||
|
*/
|
||||||
|
private async runMethod(message: Method): Promise<void> {
|
||||||
|
const proxyMessage = message.getNamedProxy()! || message.getNumberedProxy()!;
|
||||||
|
const id = proxyMessage.getId();
|
||||||
|
const proxyId = message.hasNamedProxy()
|
||||||
|
? protoToModule(message.getNamedProxy()!.getModule())
|
||||||
|
: message.getNumberedProxy()!.getProxyId();
|
||||||
|
const method = proxyMessage.getMethod();
|
||||||
|
const args = proxyMessage.getArgsList().map((a) => protoToArgument(
|
||||||
|
a,
|
||||||
|
(id, args) => this.sendCallback(proxyId, id, args),
|
||||||
|
(id) => this.getProxy(id).instance,
|
||||||
|
));
|
||||||
|
|
||||||
|
logger.trace(() => [
|
||||||
|
"received",
|
||||||
|
field("id", id),
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
field("method", method),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let response: any;
|
||||||
|
try {
|
||||||
|
const proxy = this.getProxy(proxyId);
|
||||||
|
if (typeof proxy.instance[method] !== "function") {
|
||||||
|
throw new Error(`"${method}" is not a function on proxy ${proxyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
response = proxy.instance[method](...args);
|
||||||
|
|
||||||
|
// We wait for the client to call "dispose" instead of doing it onDone to
|
||||||
|
// ensure all the messages it sent get processed before we get rid of it.
|
||||||
|
if (method === "dispose") {
|
||||||
|
this.removeProxy(proxyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxies must always return promises.
|
||||||
|
if (!isPromise(response)) {
|
||||||
|
throw new Error(`"${method}" must return a promise`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
error.message,
|
||||||
|
field("type", typeof response),
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
);
|
||||||
|
this.sendException(id, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.sendResponse(id, await response);
|
||||||
|
} catch (error) {
|
||||||
|
this.sendException(id, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a callback to the client.
|
||||||
|
*/
|
||||||
|
private sendCallback(proxyId: number | Module, callbackId: number, args: any[]): void {
|
||||||
|
logger.trace(() => [
|
||||||
|
"sending callback",
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
field("callbackId", callbackId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const message = new Callback();
|
||||||
|
let callbackMessage: Callback.Named | Callback.Numbered;
|
||||||
|
if (typeof proxyId === "string") {
|
||||||
|
callbackMessage = new Callback.Named();
|
||||||
|
callbackMessage.setModule(moduleToProto(proxyId));
|
||||||
|
message.setNamedCallback(callbackMessage);
|
||||||
|
} else {
|
||||||
|
callbackMessage = new Callback.Numbered();
|
||||||
|
callbackMessage.setProxyId(proxyId);
|
||||||
|
message.setNumberedCallback(callbackMessage);
|
||||||
|
}
|
||||||
|
callbackMessage.setCallbackId(callbackId);
|
||||||
|
callbackMessage.setArgsList(args.map((a) => this.argumentToProto(a)));
|
||||||
|
|
||||||
|
const serverMessage = new ServerMessage();
|
||||||
|
serverMessage.setCallback(message);
|
||||||
|
this.connection.send(serverMessage.serializeBinary());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a numbered proxy and bind events to send them back to the client.
|
||||||
|
*/
|
||||||
|
private storeProxy(instance: ServerProxy): number;
|
||||||
|
/**
|
||||||
|
* Store a unique proxy and bind events to send them back to the client.
|
||||||
|
*/
|
||||||
|
private storeProxy(instance: any, moduleProxyId: Module): Module;
|
||||||
|
/**
|
||||||
|
* Store a proxy and bind events to send them back to the client.
|
||||||
|
*/
|
||||||
|
private storeProxy(instance: ServerProxy | any, moduleProxyId?: Module): number | Module {
|
||||||
|
// In case we disposed while waiting for a function to return.
|
||||||
|
if (this.disconnected) {
|
||||||
|
if (isProxy(instance)) {
|
||||||
|
instance.dispose().catch((error) => {
|
||||||
|
logger.error(error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("disposed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyId = moduleProxyId || this.proxyId++;
|
||||||
|
logger.trace(() => [
|
||||||
|
"storing proxy",
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.proxies.set(proxyId, { instance });
|
||||||
|
|
||||||
|
if (isProxy(instance)) {
|
||||||
|
instance.onEvent((event, ...args) => this.sendEvent(proxyId, event, ...args));
|
||||||
|
instance.onDone(() => {
|
||||||
|
// It might have finished because we disposed it due to a disconnect.
|
||||||
|
if (!this.disconnected) {
|
||||||
|
this.sendEvent(proxyId, "done");
|
||||||
|
this.getProxy(proxyId).disposeTimeout = setTimeout(() => {
|
||||||
|
instance.dispose().catch((error) => {
|
||||||
|
logger.error(error.message);
|
||||||
|
});
|
||||||
|
this.removeProxy(proxyId);
|
||||||
|
}, this.responseTimeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event to the client.
|
||||||
|
*/
|
||||||
|
private sendEvent(proxyId: number | Module, event: string, ...args: any[]): void {
|
||||||
|
logger.trace(() => [
|
||||||
|
"sending event",
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
field("event", event),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const message = new Event();
|
||||||
|
let eventMessage: Event.Named | Event.Numbered;
|
||||||
|
if (typeof proxyId === "string") {
|
||||||
|
eventMessage = new Event.Named();
|
||||||
|
eventMessage.setModule(moduleToProto(proxyId));
|
||||||
|
message.setNamedEvent(eventMessage);
|
||||||
|
} else {
|
||||||
|
eventMessage = new Event.Numbered();
|
||||||
|
eventMessage.setProxyId(proxyId);
|
||||||
|
message.setNumberedEvent(eventMessage);
|
||||||
|
}
|
||||||
|
eventMessage.setEvent(event);
|
||||||
|
eventMessage.setArgsList(args.map((a) => this.argumentToProto(a)));
|
||||||
|
|
||||||
|
const serverMessage = new ServerMessage();
|
||||||
|
serverMessage.setEvent(message);
|
||||||
|
this.connection.send(serverMessage.serializeBinary());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a response back to the client.
|
||||||
|
*/
|
||||||
|
private sendResponse(id: number, response: any): void {
|
||||||
|
logger.trace(() => [
|
||||||
|
"sending resolve",
|
||||||
|
field("id", id),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const successMessage = new Method.Success();
|
||||||
|
successMessage.setId(id);
|
||||||
|
successMessage.setResponse(this.argumentToProto(response));
|
||||||
|
|
||||||
|
const serverMessage = new ServerMessage();
|
||||||
|
serverMessage.setSuccess(successMessage);
|
||||||
|
this.connection.send(serverMessage.serializeBinary());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an exception back to the client.
|
||||||
|
*/
|
||||||
|
private sendException(id: number, error: Error): void {
|
||||||
|
logger.trace(() => [
|
||||||
|
"sending reject",
|
||||||
|
field("id", id) ,
|
||||||
|
field("message", error.message),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const failedMessage = new Method.Fail();
|
||||||
|
failedMessage.setId(id);
|
||||||
|
failedMessage.setResponse(argumentToProto(error));
|
||||||
|
|
||||||
|
const serverMessage = new ServerMessage();
|
||||||
|
serverMessage.setFail(failedMessage);
|
||||||
|
this.connection.send(serverMessage.serializeBinary());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call after disposing a proxy.
|
||||||
|
*/
|
||||||
|
private removeProxy(proxyId: number | Module): void {
|
||||||
|
clearTimeout(this.getProxy(proxyId).disposeTimeout as any);
|
||||||
|
this.proxies.delete(proxyId);
|
||||||
|
|
||||||
|
logger.trace(() => [
|
||||||
|
"disposed and removed proxy",
|
||||||
|
field("proxyId", proxyId),
|
||||||
|
field("proxies", this.proxies.size),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as argumentToProto but provides storeProxy.
|
||||||
|
*/
|
||||||
|
private argumentToProto(value: any): Argument {
|
||||||
|
return argumentToProto(value, undefined, (p) => this.storeProxy(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a proxy. Error if it doesn't exist.
|
||||||
|
*/
|
||||||
|
private getProxy(proxyId: number | Module): ProxyData {
|
||||||
|
if (!this.proxies.has(proxyId)) {
|
||||||
|
throw new Error(`proxy ${proxyId} disposed too early`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.proxies.get(proxyId)!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,33 @@ syntax = "proto3";
|
|||||||
import "node.proto";
|
import "node.proto";
|
||||||
import "vscode.proto";
|
import "vscode.proto";
|
||||||
|
|
||||||
|
// Messages that the client can send to the server.
|
||||||
message ClientMessage {
|
message ClientMessage {
|
||||||
oneof msg {
|
oneof msg {
|
||||||
// node.proto
|
// node.proto
|
||||||
NewEvalMessage new_eval = 11;
|
Method method = 20;
|
||||||
EvalEventMessage eval_event = 12;
|
Ping ping = 21;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Messages that the server can send to the client.
|
||||||
message ServerMessage {
|
message ServerMessage {
|
||||||
oneof msg {
|
oneof msg {
|
||||||
// node.proto
|
// node.proto
|
||||||
EvalFailedMessage eval_failed = 13;
|
Method.Fail fail = 13;
|
||||||
EvalDoneMessage eval_done = 14;
|
Method.Success success = 14;
|
||||||
EvalEventMessage eval_event = 15;
|
Event event = 19;
|
||||||
|
Callback callback = 22;
|
||||||
|
Pong pong = 18;
|
||||||
|
|
||||||
WorkingInitMessage init = 16;
|
WorkingInit init = 16;
|
||||||
|
|
||||||
// vscode.proto
|
// vscode.proto
|
||||||
SharedProcessActiveMessage shared_process_active = 17;
|
SharedProcessActive shared_process_active = 17;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message WorkingInitMessage {
|
message WorkingInit {
|
||||||
string home_directory = 1;
|
string home_directory = 1;
|
||||||
string tmp_directory = 2;
|
string tmp_directory = 2;
|
||||||
string data_directory = 3;
|
string data_directory = 3;
|
||||||
@@ -37,4 +41,7 @@ message WorkingInitMessage {
|
|||||||
OperatingSystem operating_system = 5;
|
OperatingSystem operating_system = 5;
|
||||||
string shell = 6;
|
string shell = 6;
|
||||||
string builtin_extensions_dir = 7;
|
string builtin_extensions_dir = 7;
|
||||||
|
string extensions_directory = 8;
|
||||||
|
repeated string extra_extension_directories = 9;
|
||||||
|
repeated string extra_builtin_extension_directories = 10;
|
||||||
}
|
}
|
||||||
|
|||||||
122
packages/protocol/src/proto/client_pb.d.ts
vendored
@@ -6,15 +6,15 @@ import * as node_pb from "./node_pb";
|
|||||||
import * as vscode_pb from "./vscode_pb";
|
import * as vscode_pb from "./vscode_pb";
|
||||||
|
|
||||||
export class ClientMessage extends jspb.Message {
|
export class ClientMessage extends jspb.Message {
|
||||||
hasNewEval(): boolean;
|
hasMethod(): boolean;
|
||||||
clearNewEval(): void;
|
clearMethod(): void;
|
||||||
getNewEval(): node_pb.NewEvalMessage | undefined;
|
getMethod(): node_pb.Method | undefined;
|
||||||
setNewEval(value?: node_pb.NewEvalMessage): void;
|
setMethod(value?: node_pb.Method): void;
|
||||||
|
|
||||||
hasEvalEvent(): boolean;
|
hasPing(): boolean;
|
||||||
clearEvalEvent(): void;
|
clearPing(): void;
|
||||||
getEvalEvent(): node_pb.EvalEventMessage | undefined;
|
getPing(): node_pb.Ping | undefined;
|
||||||
setEvalEvent(value?: node_pb.EvalEventMessage): void;
|
setPing(value?: node_pb.Ping): void;
|
||||||
|
|
||||||
getMsgCase(): ClientMessage.MsgCase;
|
getMsgCase(): ClientMessage.MsgCase;
|
||||||
serializeBinary(): Uint8Array;
|
serializeBinary(): Uint8Array;
|
||||||
@@ -29,42 +29,52 @@ export class ClientMessage extends jspb.Message {
|
|||||||
|
|
||||||
export namespace ClientMessage {
|
export namespace ClientMessage {
|
||||||
export type AsObject = {
|
export type AsObject = {
|
||||||
newEval?: node_pb.NewEvalMessage.AsObject,
|
method?: node_pb.Method.AsObject,
|
||||||
evalEvent?: node_pb.EvalEventMessage.AsObject,
|
ping?: node_pb.Ping.AsObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MsgCase {
|
export enum MsgCase {
|
||||||
MSG_NOT_SET = 0,
|
MSG_NOT_SET = 0,
|
||||||
NEW_EVAL = 11,
|
METHOD = 20,
|
||||||
EVAL_EVENT = 12,
|
PING = 21,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServerMessage extends jspb.Message {
|
export class ServerMessage extends jspb.Message {
|
||||||
hasEvalFailed(): boolean;
|
hasFail(): boolean;
|
||||||
clearEvalFailed(): void;
|
clearFail(): void;
|
||||||
getEvalFailed(): node_pb.EvalFailedMessage | undefined;
|
getFail(): node_pb.Method.Fail | undefined;
|
||||||
setEvalFailed(value?: node_pb.EvalFailedMessage): void;
|
setFail(value?: node_pb.Method.Fail): void;
|
||||||
|
|
||||||
hasEvalDone(): boolean;
|
hasSuccess(): boolean;
|
||||||
clearEvalDone(): void;
|
clearSuccess(): void;
|
||||||
getEvalDone(): node_pb.EvalDoneMessage | undefined;
|
getSuccess(): node_pb.Method.Success | undefined;
|
||||||
setEvalDone(value?: node_pb.EvalDoneMessage): void;
|
setSuccess(value?: node_pb.Method.Success): void;
|
||||||
|
|
||||||
hasEvalEvent(): boolean;
|
hasEvent(): boolean;
|
||||||
clearEvalEvent(): void;
|
clearEvent(): void;
|
||||||
getEvalEvent(): node_pb.EvalEventMessage | undefined;
|
getEvent(): node_pb.Event | undefined;
|
||||||
setEvalEvent(value?: node_pb.EvalEventMessage): void;
|
setEvent(value?: node_pb.Event): void;
|
||||||
|
|
||||||
|
hasCallback(): boolean;
|
||||||
|
clearCallback(): void;
|
||||||
|
getCallback(): node_pb.Callback | undefined;
|
||||||
|
setCallback(value?: node_pb.Callback): void;
|
||||||
|
|
||||||
|
hasPong(): boolean;
|
||||||
|
clearPong(): void;
|
||||||
|
getPong(): node_pb.Pong | undefined;
|
||||||
|
setPong(value?: node_pb.Pong): void;
|
||||||
|
|
||||||
hasInit(): boolean;
|
hasInit(): boolean;
|
||||||
clearInit(): void;
|
clearInit(): void;
|
||||||
getInit(): WorkingInitMessage | undefined;
|
getInit(): WorkingInit | undefined;
|
||||||
setInit(value?: WorkingInitMessage): void;
|
setInit(value?: WorkingInit): void;
|
||||||
|
|
||||||
hasSharedProcessActive(): boolean;
|
hasSharedProcessActive(): boolean;
|
||||||
clearSharedProcessActive(): void;
|
clearSharedProcessActive(): void;
|
||||||
getSharedProcessActive(): vscode_pb.SharedProcessActiveMessage | undefined;
|
getSharedProcessActive(): vscode_pb.SharedProcessActive | undefined;
|
||||||
setSharedProcessActive(value?: vscode_pb.SharedProcessActiveMessage): void;
|
setSharedProcessActive(value?: vscode_pb.SharedProcessActive): void;
|
||||||
|
|
||||||
getMsgCase(): ServerMessage.MsgCase;
|
getMsgCase(): ServerMessage.MsgCase;
|
||||||
serializeBinary(): Uint8Array;
|
serializeBinary(): Uint8Array;
|
||||||
@@ -79,24 +89,28 @@ export class ServerMessage extends jspb.Message {
|
|||||||
|
|
||||||
export namespace ServerMessage {
|
export namespace ServerMessage {
|
||||||
export type AsObject = {
|
export type AsObject = {
|
||||||
evalFailed?: node_pb.EvalFailedMessage.AsObject,
|
fail?: node_pb.Method.Fail.AsObject,
|
||||||
evalDone?: node_pb.EvalDoneMessage.AsObject,
|
success?: node_pb.Method.Success.AsObject,
|
||||||
evalEvent?: node_pb.EvalEventMessage.AsObject,
|
event?: node_pb.Event.AsObject,
|
||||||
init?: WorkingInitMessage.AsObject,
|
callback?: node_pb.Callback.AsObject,
|
||||||
sharedProcessActive?: vscode_pb.SharedProcessActiveMessage.AsObject,
|
pong?: node_pb.Pong.AsObject,
|
||||||
|
init?: WorkingInit.AsObject,
|
||||||
|
sharedProcessActive?: vscode_pb.SharedProcessActive.AsObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MsgCase {
|
export enum MsgCase {
|
||||||
MSG_NOT_SET = 0,
|
MSG_NOT_SET = 0,
|
||||||
EVAL_FAILED = 13,
|
FAIL = 13,
|
||||||
EVAL_DONE = 14,
|
SUCCESS = 14,
|
||||||
EVAL_EVENT = 15,
|
EVENT = 19,
|
||||||
|
CALLBACK = 22,
|
||||||
|
PONG = 18,
|
||||||
INIT = 16,
|
INIT = 16,
|
||||||
SHARED_PROCESS_ACTIVE = 17,
|
SHARED_PROCESS_ACTIVE = 17,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkingInitMessage extends jspb.Message {
|
export class WorkingInit extends jspb.Message {
|
||||||
getHomeDirectory(): string;
|
getHomeDirectory(): string;
|
||||||
setHomeDirectory(value: string): void;
|
setHomeDirectory(value: string): void;
|
||||||
|
|
||||||
@@ -109,8 +123,8 @@ export class WorkingInitMessage extends jspb.Message {
|
|||||||
getWorkingDirectory(): string;
|
getWorkingDirectory(): string;
|
||||||
setWorkingDirectory(value: string): void;
|
setWorkingDirectory(value: string): void;
|
||||||
|
|
||||||
getOperatingSystem(): WorkingInitMessage.OperatingSystem;
|
getOperatingSystem(): WorkingInit.OperatingSystem;
|
||||||
setOperatingSystem(value: WorkingInitMessage.OperatingSystem): void;
|
setOperatingSystem(value: WorkingInit.OperatingSystem): void;
|
||||||
|
|
||||||
getShell(): string;
|
getShell(): string;
|
||||||
setShell(value: string): void;
|
setShell(value: string): void;
|
||||||
@@ -118,25 +132,41 @@ export class WorkingInitMessage extends jspb.Message {
|
|||||||
getBuiltinExtensionsDir(): string;
|
getBuiltinExtensionsDir(): string;
|
||||||
setBuiltinExtensionsDir(value: string): void;
|
setBuiltinExtensionsDir(value: string): void;
|
||||||
|
|
||||||
|
getExtensionsDirectory(): string;
|
||||||
|
setExtensionsDirectory(value: string): void;
|
||||||
|
|
||||||
|
clearExtraExtensionDirectoriesList(): void;
|
||||||
|
getExtraExtensionDirectoriesList(): Array<string>;
|
||||||
|
setExtraExtensionDirectoriesList(value: Array<string>): void;
|
||||||
|
addExtraExtensionDirectories(value: string, index?: number): string;
|
||||||
|
|
||||||
|
clearExtraBuiltinExtensionDirectoriesList(): void;
|
||||||
|
getExtraBuiltinExtensionDirectoriesList(): Array<string>;
|
||||||
|
setExtraBuiltinExtensionDirectoriesList(value: Array<string>): void;
|
||||||
|
addExtraBuiltinExtensionDirectories(value: string, index?: number): string;
|
||||||
|
|
||||||
serializeBinary(): Uint8Array;
|
serializeBinary(): Uint8Array;
|
||||||
toObject(includeInstance?: boolean): WorkingInitMessage.AsObject;
|
toObject(includeInstance?: boolean): WorkingInit.AsObject;
|
||||||
static toObject(includeInstance: boolean, msg: WorkingInitMessage): WorkingInitMessage.AsObject;
|
static toObject(includeInstance: boolean, msg: WorkingInit): WorkingInit.AsObject;
|
||||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
static serializeBinaryToWriter(message: WorkingInitMessage, writer: jspb.BinaryWriter): void;
|
static serializeBinaryToWriter(message: WorkingInit, writer: jspb.BinaryWriter): void;
|
||||||
static deserializeBinary(bytes: Uint8Array): WorkingInitMessage;
|
static deserializeBinary(bytes: Uint8Array): WorkingInit;
|
||||||
static deserializeBinaryFromReader(message: WorkingInitMessage, reader: jspb.BinaryReader): WorkingInitMessage;
|
static deserializeBinaryFromReader(message: WorkingInit, reader: jspb.BinaryReader): WorkingInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace WorkingInitMessage {
|
export namespace WorkingInit {
|
||||||
export type AsObject = {
|
export type AsObject = {
|
||||||
homeDirectory: string,
|
homeDirectory: string,
|
||||||
tmpDirectory: string,
|
tmpDirectory: string,
|
||||||
dataDirectory: string,
|
dataDirectory: string,
|
||||||
workingDirectory: string,
|
workingDirectory: string,
|
||||||
operatingSystem: WorkingInitMessage.OperatingSystem,
|
operatingSystem: WorkingInit.OperatingSystem,
|
||||||
shell: string,
|
shell: string,
|
||||||
builtinExtensionsDir: string,
|
builtinExtensionsDir: string,
|
||||||
|
extensionsDirectory: string,
|
||||||
|
extraExtensionDirectoriesList: Array<string>,
|
||||||
|
extraBuiltinExtensionDirectoriesList: Array<string>,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OperatingSystem {
|
export enum OperatingSystem {
|
||||||
|
|||||||
@@ -1,28 +1,143 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
message NewEvalMessage {
|
enum Module {
|
||||||
uint64 id = 1;
|
ChildProcess = 0;
|
||||||
string function = 2;
|
Fs = 1;
|
||||||
repeated string args = 3;
|
Net = 2;
|
||||||
// Timeout in ms
|
NodePty = 3;
|
||||||
uint32 timeout = 4;
|
Spdlog = 4;
|
||||||
// Create active eval message.
|
Trash = 5;
|
||||||
// Allows for dynamic communication for an eval
|
|
||||||
bool active = 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message EvalEventMessage {
|
message Argument {
|
||||||
uint64 id = 1;
|
message ErrorValue {
|
||||||
string event = 2;
|
string message = 1;
|
||||||
repeated string args = 3;
|
string stack = 2;
|
||||||
|
string code = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BufferValue {
|
||||||
|
bytes data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ObjectValue {
|
||||||
|
map<string, Argument> data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ArrayValue {
|
||||||
|
repeated Argument data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProxyValue {
|
||||||
|
uint64 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FunctionValue {
|
||||||
|
uint64 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NullValue {}
|
||||||
|
|
||||||
|
message UndefinedValue {}
|
||||||
|
|
||||||
|
message DateValue {
|
||||||
|
string date = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
oneof msg {
|
||||||
|
ErrorValue error = 1;
|
||||||
|
BufferValue buffer = 2;
|
||||||
|
ObjectValue object = 3;
|
||||||
|
ArrayValue array = 4;
|
||||||
|
ProxyValue proxy = 5;
|
||||||
|
FunctionValue function = 6;
|
||||||
|
NullValue null = 7;
|
||||||
|
UndefinedValue undefined = 8;
|
||||||
|
double number = 9;
|
||||||
|
string string = 10;
|
||||||
|
bool boolean = 11;
|
||||||
|
DateValue date = 12;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message EvalFailedMessage {
|
// Call a remote method.
|
||||||
uint64 id = 1;
|
message Method {
|
||||||
string response = 2;
|
// A proxy identified by a unique name like "fs".
|
||||||
|
message Named {
|
||||||
|
uint64 id = 1;
|
||||||
|
Module module = 2;
|
||||||
|
string method = 3;
|
||||||
|
repeated Argument args = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A general proxy identified by an ID like WriteStream.
|
||||||
|
message Numbered {
|
||||||
|
uint64 id = 1;
|
||||||
|
uint64 proxy_id = 2;
|
||||||
|
string method = 3;
|
||||||
|
repeated Argument args = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote method failed.
|
||||||
|
message Fail {
|
||||||
|
uint64 id = 1;
|
||||||
|
Argument response = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote method succeeded.
|
||||||
|
message Success {
|
||||||
|
uint64 id = 1;
|
||||||
|
Argument response = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
oneof msg {
|
||||||
|
Method.Named named_proxy = 1;
|
||||||
|
Method.Numbered numbered_proxy = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message EvalDoneMessage {
|
message Callback {
|
||||||
uint64 id = 1;
|
// A remote callback for uniquely named proxy.
|
||||||
string response = 2;
|
message Named {
|
||||||
|
Module module = 1;
|
||||||
|
uint64 callback_id = 2;
|
||||||
|
repeated Argument args = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A remote callback for a numbered proxy.
|
||||||
|
message Numbered {
|
||||||
|
uint64 proxy_id = 1;
|
||||||
|
uint64 callback_id = 2;
|
||||||
|
repeated Argument args = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
oneof msg {
|
||||||
|
Callback.Named named_callback = 1;
|
||||||
|
Callback.Numbered numbered_callback = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Event {
|
||||||
|
// Emit an event on a uniquely named proxy.
|
||||||
|
message Named {
|
||||||
|
Module module = 1;
|
||||||
|
string event = 2;
|
||||||
|
repeated Argument args = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit an event on a numbered proxy.
|
||||||
|
message Numbered {
|
||||||
|
uint64 proxy_id = 1;
|
||||||
|
string event = 2;
|
||||||
|
repeated Argument args = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
oneof msg {
|
||||||
|
Event.Named named_event = 1;
|
||||||
|
Event.Numbered numbered_event = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Ping {}
|
||||||
|
|
||||||
|
message Pong {}
|
||||||
|
|||||||
696
packages/protocol/src/proto/node_pb.d.ts
vendored
@@ -3,119 +3,677 @@
|
|||||||
|
|
||||||
import * as jspb from "google-protobuf";
|
import * as jspb from "google-protobuf";
|
||||||
|
|
||||||
export class NewEvalMessage extends jspb.Message {
|
export class Argument extends jspb.Message {
|
||||||
getId(): number;
|
hasError(): boolean;
|
||||||
setId(value: number): void;
|
clearError(): void;
|
||||||
|
getError(): Argument.ErrorValue | undefined;
|
||||||
|
setError(value?: Argument.ErrorValue): void;
|
||||||
|
|
||||||
getFunction(): string;
|
hasBuffer(): boolean;
|
||||||
setFunction(value: string): void;
|
clearBuffer(): void;
|
||||||
|
getBuffer(): Argument.BufferValue | undefined;
|
||||||
|
setBuffer(value?: Argument.BufferValue): void;
|
||||||
|
|
||||||
clearArgsList(): void;
|
hasObject(): boolean;
|
||||||
getArgsList(): Array<string>;
|
clearObject(): void;
|
||||||
setArgsList(value: Array<string>): void;
|
getObject(): Argument.ObjectValue | undefined;
|
||||||
addArgs(value: string, index?: number): string;
|
setObject(value?: Argument.ObjectValue): void;
|
||||||
|
|
||||||
getTimeout(): number;
|
hasArray(): boolean;
|
||||||
setTimeout(value: number): void;
|
clearArray(): void;
|
||||||
|
getArray(): Argument.ArrayValue | undefined;
|
||||||
|
setArray(value?: Argument.ArrayValue): void;
|
||||||
|
|
||||||
getActive(): boolean;
|
hasProxy(): boolean;
|
||||||
setActive(value: boolean): void;
|
clearProxy(): void;
|
||||||
|
getProxy(): Argument.ProxyValue | undefined;
|
||||||
|
setProxy(value?: Argument.ProxyValue): void;
|
||||||
|
|
||||||
|
hasFunction(): boolean;
|
||||||
|
clearFunction(): void;
|
||||||
|
getFunction(): Argument.FunctionValue | undefined;
|
||||||
|
setFunction(value?: Argument.FunctionValue): void;
|
||||||
|
|
||||||
|
hasNull(): boolean;
|
||||||
|
clearNull(): void;
|
||||||
|
getNull(): Argument.NullValue | undefined;
|
||||||
|
setNull(value?: Argument.NullValue): void;
|
||||||
|
|
||||||
|
hasUndefined(): boolean;
|
||||||
|
clearUndefined(): void;
|
||||||
|
getUndefined(): Argument.UndefinedValue | undefined;
|
||||||
|
setUndefined(value?: Argument.UndefinedValue): void;
|
||||||
|
|
||||||
|
hasNumber(): boolean;
|
||||||
|
clearNumber(): void;
|
||||||
|
getNumber(): number;
|
||||||
|
setNumber(value: number): void;
|
||||||
|
|
||||||
|
hasString(): boolean;
|
||||||
|
clearString(): void;
|
||||||
|
getString(): string;
|
||||||
|
setString(value: string): void;
|
||||||
|
|
||||||
|
hasBoolean(): boolean;
|
||||||
|
clearBoolean(): void;
|
||||||
|
getBoolean(): boolean;
|
||||||
|
setBoolean(value: boolean): void;
|
||||||
|
|
||||||
|
hasDate(): boolean;
|
||||||
|
clearDate(): void;
|
||||||
|
getDate(): Argument.DateValue | undefined;
|
||||||
|
setDate(value?: Argument.DateValue): void;
|
||||||
|
|
||||||
|
getMsgCase(): Argument.MsgCase;
|
||||||
serializeBinary(): Uint8Array;
|
serializeBinary(): Uint8Array;
|
||||||
toObject(includeInstance?: boolean): NewEvalMessage.AsObject;
|
toObject(includeInstance?: boolean): Argument.AsObject;
|
||||||
static toObject(includeInstance: boolean, msg: NewEvalMessage): NewEvalMessage.AsObject;
|
static toObject(includeInstance: boolean, msg: Argument): Argument.AsObject;
|
||||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
static serializeBinaryToWriter(message: NewEvalMessage, writer: jspb.BinaryWriter): void;
|
static serializeBinaryToWriter(message: Argument, writer: jspb.BinaryWriter): void;
|
||||||
static deserializeBinary(bytes: Uint8Array): NewEvalMessage;
|
static deserializeBinary(bytes: Uint8Array): Argument;
|
||||||
static deserializeBinaryFromReader(message: NewEvalMessage, reader: jspb.BinaryReader): NewEvalMessage;
|
static deserializeBinaryFromReader(message: Argument, reader: jspb.BinaryReader): Argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace NewEvalMessage {
|
export namespace Argument {
|
||||||
export type AsObject = {
|
export type AsObject = {
|
||||||
id: number,
|
error?: Argument.ErrorValue.AsObject,
|
||||||
pb_function: string,
|
buffer?: Argument.BufferValue.AsObject,
|
||||||
argsList: Array<string>,
|
object?: Argument.ObjectValue.AsObject,
|
||||||
timeout: number,
|
array?: Argument.ArrayValue.AsObject,
|
||||||
active: boolean,
|
proxy?: Argument.ProxyValue.AsObject,
|
||||||
|
pb_function?: Argument.FunctionValue.AsObject,
|
||||||
|
pb_null?: Argument.NullValue.AsObject,
|
||||||
|
undefined?: Argument.UndefinedValue.AsObject,
|
||||||
|
number: number,
|
||||||
|
string: string,
|
||||||
|
pb_boolean: boolean,
|
||||||
|
date?: Argument.DateValue.AsObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorValue extends jspb.Message {
|
||||||
|
getMessage(): string;
|
||||||
|
setMessage(value: string): void;
|
||||||
|
|
||||||
|
getStack(): string;
|
||||||
|
setStack(value: string): void;
|
||||||
|
|
||||||
|
getCode(): string;
|
||||||
|
setCode(value: string): void;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): ErrorValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: ErrorValue): ErrorValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: ErrorValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): ErrorValue;
|
||||||
|
static deserializeBinaryFromReader(message: ErrorValue, reader: jspb.BinaryReader): ErrorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ErrorValue {
|
||||||
|
export type AsObject = {
|
||||||
|
message: string,
|
||||||
|
stack: string,
|
||||||
|
code: string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BufferValue extends jspb.Message {
|
||||||
|
getData(): Uint8Array | string;
|
||||||
|
getData_asU8(): Uint8Array;
|
||||||
|
getData_asB64(): string;
|
||||||
|
setData(value: Uint8Array | string): void;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): BufferValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: BufferValue): BufferValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: BufferValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): BufferValue;
|
||||||
|
static deserializeBinaryFromReader(message: BufferValue, reader: jspb.BinaryReader): BufferValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace BufferValue {
|
||||||
|
export type AsObject = {
|
||||||
|
data: Uint8Array | string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ObjectValue extends jspb.Message {
|
||||||
|
getDataMap(): jspb.Map<string, Argument>;
|
||||||
|
clearDataMap(): void;
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): ObjectValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: ObjectValue): ObjectValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: ObjectValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): ObjectValue;
|
||||||
|
static deserializeBinaryFromReader(message: ObjectValue, reader: jspb.BinaryReader): ObjectValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ObjectValue {
|
||||||
|
export type AsObject = {
|
||||||
|
dataMap: Array<[string, Argument.AsObject]>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArrayValue extends jspb.Message {
|
||||||
|
clearDataList(): void;
|
||||||
|
getDataList(): Array<Argument>;
|
||||||
|
setDataList(value: Array<Argument>): void;
|
||||||
|
addData(value?: Argument, index?: number): Argument;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): ArrayValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: ArrayValue): ArrayValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: ArrayValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): ArrayValue;
|
||||||
|
static deserializeBinaryFromReader(message: ArrayValue, reader: jspb.BinaryReader): ArrayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ArrayValue {
|
||||||
|
export type AsObject = {
|
||||||
|
dataList: Array<Argument.AsObject>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProxyValue extends jspb.Message {
|
||||||
|
getId(): number;
|
||||||
|
setId(value: number): void;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): ProxyValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: ProxyValue): ProxyValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: ProxyValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): ProxyValue;
|
||||||
|
static deserializeBinaryFromReader(message: ProxyValue, reader: jspb.BinaryReader): ProxyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ProxyValue {
|
||||||
|
export type AsObject = {
|
||||||
|
id: number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FunctionValue extends jspb.Message {
|
||||||
|
getId(): number;
|
||||||
|
setId(value: number): void;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): FunctionValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: FunctionValue): FunctionValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: FunctionValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): FunctionValue;
|
||||||
|
static deserializeBinaryFromReader(message: FunctionValue, reader: jspb.BinaryReader): FunctionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace FunctionValue {
|
||||||
|
export type AsObject = {
|
||||||
|
id: number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NullValue extends jspb.Message {
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): NullValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: NullValue): NullValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: NullValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): NullValue;
|
||||||
|
static deserializeBinaryFromReader(message: NullValue, reader: jspb.BinaryReader): NullValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace NullValue {
|
||||||
|
export type AsObject = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UndefinedValue extends jspb.Message {
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): UndefinedValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: UndefinedValue): UndefinedValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: UndefinedValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): UndefinedValue;
|
||||||
|
static deserializeBinaryFromReader(message: UndefinedValue, reader: jspb.BinaryReader): UndefinedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace UndefinedValue {
|
||||||
|
export type AsObject = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DateValue extends jspb.Message {
|
||||||
|
getDate(): string;
|
||||||
|
setDate(value: string): void;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): DateValue.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: DateValue): DateValue.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: DateValue, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): DateValue;
|
||||||
|
static deserializeBinaryFromReader(message: DateValue, reader: jspb.BinaryReader): DateValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace DateValue {
|
||||||
|
export type AsObject = {
|
||||||
|
date: string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MsgCase {
|
||||||
|
MSG_NOT_SET = 0,
|
||||||
|
ERROR = 1,
|
||||||
|
BUFFER = 2,
|
||||||
|
OBJECT = 3,
|
||||||
|
ARRAY = 4,
|
||||||
|
PROXY = 5,
|
||||||
|
FUNCTION = 6,
|
||||||
|
NULL = 7,
|
||||||
|
UNDEFINED = 8,
|
||||||
|
NUMBER = 9,
|
||||||
|
STRING = 10,
|
||||||
|
BOOLEAN = 11,
|
||||||
|
DATE = 12,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EvalEventMessage extends jspb.Message {
|
export class Method extends jspb.Message {
|
||||||
getId(): number;
|
hasNamedProxy(): boolean;
|
||||||
setId(value: number): void;
|
clearNamedProxy(): void;
|
||||||
|
getNamedProxy(): Method.Named | undefined;
|
||||||
|
setNamedProxy(value?: Method.Named): void;
|
||||||
|
|
||||||
getEvent(): string;
|
hasNumberedProxy(): boolean;
|
||||||
setEvent(value: string): void;
|
clearNumberedProxy(): void;
|
||||||
|
getNumberedProxy(): Method.Numbered | undefined;
|
||||||
clearArgsList(): void;
|
setNumberedProxy(value?: Method.Numbered): void;
|
||||||
getArgsList(): Array<string>;
|
|
||||||
setArgsList(value: Array<string>): void;
|
|
||||||
addArgs(value: string, index?: number): string;
|
|
||||||
|
|
||||||
|
getMsgCase(): Method.MsgCase;
|
||||||
serializeBinary(): Uint8Array;
|
serializeBinary(): Uint8Array;
|
||||||
toObject(includeInstance?: boolean): EvalEventMessage.AsObject;
|
toObject(includeInstance?: boolean): Method.AsObject;
|
||||||
static toObject(includeInstance: boolean, msg: EvalEventMessage): EvalEventMessage.AsObject;
|
static toObject(includeInstance: boolean, msg: Method): Method.AsObject;
|
||||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
static serializeBinaryToWriter(message: EvalEventMessage, writer: jspb.BinaryWriter): void;
|
static serializeBinaryToWriter(message: Method, writer: jspb.BinaryWriter): void;
|
||||||
static deserializeBinary(bytes: Uint8Array): EvalEventMessage;
|
static deserializeBinary(bytes: Uint8Array): Method;
|
||||||
static deserializeBinaryFromReader(message: EvalEventMessage, reader: jspb.BinaryReader): EvalEventMessage;
|
static deserializeBinaryFromReader(message: Method, reader: jspb.BinaryReader): Method;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace EvalEventMessage {
|
export namespace Method {
|
||||||
export type AsObject = {
|
export type AsObject = {
|
||||||
id: number,
|
namedProxy?: Method.Named.AsObject,
|
||||||
event: string,
|
numberedProxy?: Method.Numbered.AsObject,
|
||||||
argsList: Array<string>,
|
}
|
||||||
|
|
||||||
|
export class Named extends jspb.Message {
|
||||||
|
getId(): number;
|
||||||
|
setId(value: number): void;
|
||||||
|
|
||||||
|
getModule(): Module;
|
||||||
|
setModule(value: Module): void;
|
||||||
|
|
||||||
|
getMethod(): string;
|
||||||
|
setMethod(value: string): void;
|
||||||
|
|
||||||
|
clearArgsList(): void;
|
||||||
|
getArgsList(): Array<Argument>;
|
||||||
|
setArgsList(value: Array<Argument>): void;
|
||||||
|
addArgs(value?: Argument, index?: number): Argument;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Named.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Named): Named.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Named, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Named;
|
||||||
|
static deserializeBinaryFromReader(message: Named, reader: jspb.BinaryReader): Named;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Named {
|
||||||
|
export type AsObject = {
|
||||||
|
id: number,
|
||||||
|
module: Module,
|
||||||
|
method: string,
|
||||||
|
argsList: Array<Argument.AsObject>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Numbered extends jspb.Message {
|
||||||
|
getId(): number;
|
||||||
|
setId(value: number): void;
|
||||||
|
|
||||||
|
getProxyId(): number;
|
||||||
|
setProxyId(value: number): void;
|
||||||
|
|
||||||
|
getMethod(): string;
|
||||||
|
setMethod(value: string): void;
|
||||||
|
|
||||||
|
clearArgsList(): void;
|
||||||
|
getArgsList(): Array<Argument>;
|
||||||
|
setArgsList(value: Array<Argument>): void;
|
||||||
|
addArgs(value?: Argument, index?: number): Argument;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Numbered.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Numbered): Numbered.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Numbered, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Numbered;
|
||||||
|
static deserializeBinaryFromReader(message: Numbered, reader: jspb.BinaryReader): Numbered;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Numbered {
|
||||||
|
export type AsObject = {
|
||||||
|
id: number,
|
||||||
|
proxyId: number,
|
||||||
|
method: string,
|
||||||
|
argsList: Array<Argument.AsObject>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Fail extends jspb.Message {
|
||||||
|
getId(): number;
|
||||||
|
setId(value: number): void;
|
||||||
|
|
||||||
|
hasResponse(): boolean;
|
||||||
|
clearResponse(): void;
|
||||||
|
getResponse(): Argument | undefined;
|
||||||
|
setResponse(value?: Argument): void;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Fail.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Fail): Fail.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Fail, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Fail;
|
||||||
|
static deserializeBinaryFromReader(message: Fail, reader: jspb.BinaryReader): Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Fail {
|
||||||
|
export type AsObject = {
|
||||||
|
id: number,
|
||||||
|
response?: Argument.AsObject,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Success extends jspb.Message {
|
||||||
|
getId(): number;
|
||||||
|
setId(value: number): void;
|
||||||
|
|
||||||
|
hasResponse(): boolean;
|
||||||
|
clearResponse(): void;
|
||||||
|
getResponse(): Argument | undefined;
|
||||||
|
setResponse(value?: Argument): void;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Success.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Success): Success.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Success, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Success;
|
||||||
|
static deserializeBinaryFromReader(message: Success, reader: jspb.BinaryReader): Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Success {
|
||||||
|
export type AsObject = {
|
||||||
|
id: number,
|
||||||
|
response?: Argument.AsObject,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MsgCase {
|
||||||
|
MSG_NOT_SET = 0,
|
||||||
|
NAMED_PROXY = 1,
|
||||||
|
NUMBERED_PROXY = 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EvalFailedMessage extends jspb.Message {
|
export class Callback extends jspb.Message {
|
||||||
getId(): number;
|
hasNamedCallback(): boolean;
|
||||||
setId(value: number): void;
|
clearNamedCallback(): void;
|
||||||
|
getNamedCallback(): Callback.Named | undefined;
|
||||||
|
setNamedCallback(value?: Callback.Named): void;
|
||||||
|
|
||||||
getResponse(): string;
|
hasNumberedCallback(): boolean;
|
||||||
setResponse(value: string): void;
|
clearNumberedCallback(): void;
|
||||||
|
getNumberedCallback(): Callback.Numbered | undefined;
|
||||||
|
setNumberedCallback(value?: Callback.Numbered): void;
|
||||||
|
|
||||||
|
getMsgCase(): Callback.MsgCase;
|
||||||
serializeBinary(): Uint8Array;
|
serializeBinary(): Uint8Array;
|
||||||
toObject(includeInstance?: boolean): EvalFailedMessage.AsObject;
|
toObject(includeInstance?: boolean): Callback.AsObject;
|
||||||
static toObject(includeInstance: boolean, msg: EvalFailedMessage): EvalFailedMessage.AsObject;
|
static toObject(includeInstance: boolean, msg: Callback): Callback.AsObject;
|
||||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
static serializeBinaryToWriter(message: EvalFailedMessage, writer: jspb.BinaryWriter): void;
|
static serializeBinaryToWriter(message: Callback, writer: jspb.BinaryWriter): void;
|
||||||
static deserializeBinary(bytes: Uint8Array): EvalFailedMessage;
|
static deserializeBinary(bytes: Uint8Array): Callback;
|
||||||
static deserializeBinaryFromReader(message: EvalFailedMessage, reader: jspb.BinaryReader): EvalFailedMessage;
|
static deserializeBinaryFromReader(message: Callback, reader: jspb.BinaryReader): Callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace EvalFailedMessage {
|
export namespace Callback {
|
||||||
export type AsObject = {
|
export type AsObject = {
|
||||||
id: number,
|
namedCallback?: Callback.Named.AsObject,
|
||||||
response: string,
|
numberedCallback?: Callback.Numbered.AsObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Named extends jspb.Message {
|
||||||
|
getModule(): Module;
|
||||||
|
setModule(value: Module): void;
|
||||||
|
|
||||||
|
getCallbackId(): number;
|
||||||
|
setCallbackId(value: number): void;
|
||||||
|
|
||||||
|
clearArgsList(): void;
|
||||||
|
getArgsList(): Array<Argument>;
|
||||||
|
setArgsList(value: Array<Argument>): void;
|
||||||
|
addArgs(value?: Argument, index?: number): Argument;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Named.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Named): Named.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Named, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Named;
|
||||||
|
static deserializeBinaryFromReader(message: Named, reader: jspb.BinaryReader): Named;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Named {
|
||||||
|
export type AsObject = {
|
||||||
|
module: Module,
|
||||||
|
callbackId: number,
|
||||||
|
argsList: Array<Argument.AsObject>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Numbered extends jspb.Message {
|
||||||
|
getProxyId(): number;
|
||||||
|
setProxyId(value: number): void;
|
||||||
|
|
||||||
|
getCallbackId(): number;
|
||||||
|
setCallbackId(value: number): void;
|
||||||
|
|
||||||
|
clearArgsList(): void;
|
||||||
|
getArgsList(): Array<Argument>;
|
||||||
|
setArgsList(value: Array<Argument>): void;
|
||||||
|
addArgs(value?: Argument, index?: number): Argument;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Numbered.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Numbered): Numbered.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Numbered, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Numbered;
|
||||||
|
static deserializeBinaryFromReader(message: Numbered, reader: jspb.BinaryReader): Numbered;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Numbered {
|
||||||
|
export type AsObject = {
|
||||||
|
proxyId: number,
|
||||||
|
callbackId: number,
|
||||||
|
argsList: Array<Argument.AsObject>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MsgCase {
|
||||||
|
MSG_NOT_SET = 0,
|
||||||
|
NAMED_CALLBACK = 1,
|
||||||
|
NUMBERED_CALLBACK = 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EvalDoneMessage extends jspb.Message {
|
export class Event extends jspb.Message {
|
||||||
getId(): number;
|
hasNamedEvent(): boolean;
|
||||||
setId(value: number): void;
|
clearNamedEvent(): void;
|
||||||
|
getNamedEvent(): Event.Named | undefined;
|
||||||
|
setNamedEvent(value?: Event.Named): void;
|
||||||
|
|
||||||
getResponse(): string;
|
hasNumberedEvent(): boolean;
|
||||||
setResponse(value: string): void;
|
clearNumberedEvent(): void;
|
||||||
|
getNumberedEvent(): Event.Numbered | undefined;
|
||||||
|
setNumberedEvent(value?: Event.Numbered): void;
|
||||||
|
|
||||||
|
getMsgCase(): Event.MsgCase;
|
||||||
serializeBinary(): Uint8Array;
|
serializeBinary(): Uint8Array;
|
||||||
toObject(includeInstance?: boolean): EvalDoneMessage.AsObject;
|
toObject(includeInstance?: boolean): Event.AsObject;
|
||||||
static toObject(includeInstance: boolean, msg: EvalDoneMessage): EvalDoneMessage.AsObject;
|
static toObject(includeInstance: boolean, msg: Event): Event.AsObject;
|
||||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
static serializeBinaryToWriter(message: EvalDoneMessage, writer: jspb.BinaryWriter): void;
|
static serializeBinaryToWriter(message: Event, writer: jspb.BinaryWriter): void;
|
||||||
static deserializeBinary(bytes: Uint8Array): EvalDoneMessage;
|
static deserializeBinary(bytes: Uint8Array): Event;
|
||||||
static deserializeBinaryFromReader(message: EvalDoneMessage, reader: jspb.BinaryReader): EvalDoneMessage;
|
static deserializeBinaryFromReader(message: Event, reader: jspb.BinaryReader): Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace EvalDoneMessage {
|
export namespace Event {
|
||||||
export type AsObject = {
|
export type AsObject = {
|
||||||
id: number,
|
namedEvent?: Event.Named.AsObject,
|
||||||
response: string,
|
numberedEvent?: Event.Numbered.AsObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Named extends jspb.Message {
|
||||||
|
getModule(): Module;
|
||||||
|
setModule(value: Module): void;
|
||||||
|
|
||||||
|
getEvent(): string;
|
||||||
|
setEvent(value: string): void;
|
||||||
|
|
||||||
|
clearArgsList(): void;
|
||||||
|
getArgsList(): Array<Argument>;
|
||||||
|
setArgsList(value: Array<Argument>): void;
|
||||||
|
addArgs(value?: Argument, index?: number): Argument;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Named.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Named): Named.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Named, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Named;
|
||||||
|
static deserializeBinaryFromReader(message: Named, reader: jspb.BinaryReader): Named;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Named {
|
||||||
|
export type AsObject = {
|
||||||
|
module: Module,
|
||||||
|
event: string,
|
||||||
|
argsList: Array<Argument.AsObject>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Numbered extends jspb.Message {
|
||||||
|
getProxyId(): number;
|
||||||
|
setProxyId(value: number): void;
|
||||||
|
|
||||||
|
getEvent(): string;
|
||||||
|
setEvent(value: string): void;
|
||||||
|
|
||||||
|
clearArgsList(): void;
|
||||||
|
getArgsList(): Array<Argument>;
|
||||||
|
setArgsList(value: Array<Argument>): void;
|
||||||
|
addArgs(value?: Argument, index?: number): Argument;
|
||||||
|
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Numbered.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Numbered): Numbered.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Numbered, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Numbered;
|
||||||
|
static deserializeBinaryFromReader(message: Numbered, reader: jspb.BinaryReader): Numbered;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Numbered {
|
||||||
|
export type AsObject = {
|
||||||
|
proxyId: number,
|
||||||
|
event: string,
|
||||||
|
argsList: Array<Argument.AsObject>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MsgCase {
|
||||||
|
MSG_NOT_SET = 0,
|
||||||
|
NAMED_EVENT = 1,
|
||||||
|
NUMBERED_EVENT = 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Ping extends jspb.Message {
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Ping.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Ping): Ping.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Ping, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Ping;
|
||||||
|
static deserializeBinaryFromReader(message: Ping, reader: jspb.BinaryReader): Ping;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Ping {
|
||||||
|
export type AsObject = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Pong extends jspb.Message {
|
||||||
|
serializeBinary(): Uint8Array;
|
||||||
|
toObject(includeInstance?: boolean): Pong.AsObject;
|
||||||
|
static toObject(includeInstance: boolean, msg: Pong): Pong.AsObject;
|
||||||
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
|
static serializeBinaryToWriter(message: Pong, writer: jspb.BinaryWriter): void;
|
||||||
|
static deserializeBinary(bytes: Uint8Array): Pong;
|
||||||
|
static deserializeBinaryFromReader(message: Pong, reader: jspb.BinaryReader): Pong;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Pong {
|
||||||
|
export type AsObject = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Module {
|
||||||
|
CHILDPROCESS = 0,
|
||||||
|
FS = 1,
|
||||||
|
NET = 2,
|
||||||
|
NODEPTY = 3,
|
||||||
|
SPDLOG = 4,
|
||||||
|
TRASH = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
// Sent when a shared process becomes active
|
// Sent when a shared process becomes active
|
||||||
message SharedProcessActiveMessage {
|
message SharedProcessActive {
|
||||||
string socket_path = 1;
|
string socket_path = 1;
|
||||||
string log_path = 2;
|
string log_path = 2;
|
||||||
}
|
}
|
||||||
|
|||||||
14
packages/protocol/src/proto/vscode_pb.d.ts
vendored
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import * as jspb from "google-protobuf";
|
import * as jspb from "google-protobuf";
|
||||||
|
|
||||||
export class SharedProcessActiveMessage extends jspb.Message {
|
export class SharedProcessActive extends jspb.Message {
|
||||||
getSocketPath(): string;
|
getSocketPath(): string;
|
||||||
setSocketPath(value: string): void;
|
setSocketPath(value: string): void;
|
||||||
|
|
||||||
@@ -11,16 +11,16 @@ export class SharedProcessActiveMessage extends jspb.Message {
|
|||||||
setLogPath(value: string): void;
|
setLogPath(value: string): void;
|
||||||
|
|
||||||
serializeBinary(): Uint8Array;
|
serializeBinary(): Uint8Array;
|
||||||
toObject(includeInstance?: boolean): SharedProcessActiveMessage.AsObject;
|
toObject(includeInstance?: boolean): SharedProcessActive.AsObject;
|
||||||
static toObject(includeInstance: boolean, msg: SharedProcessActiveMessage): SharedProcessActiveMessage.AsObject;
|
static toObject(includeInstance: boolean, msg: SharedProcessActive): SharedProcessActive.AsObject;
|
||||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||||
static serializeBinaryToWriter(message: SharedProcessActiveMessage, writer: jspb.BinaryWriter): void;
|
static serializeBinaryToWriter(message: SharedProcessActive, writer: jspb.BinaryWriter): void;
|
||||||
static deserializeBinary(bytes: Uint8Array): SharedProcessActiveMessage;
|
static deserializeBinary(bytes: Uint8Array): SharedProcessActive;
|
||||||
static deserializeBinaryFromReader(message: SharedProcessActiveMessage, reader: jspb.BinaryReader): SharedProcessActiveMessage;
|
static deserializeBinaryFromReader(message: SharedProcessActive, reader: jspb.BinaryReader): SharedProcessActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace SharedProcessActiveMessage {
|
export namespace SharedProcessActive {
|
||||||
export type AsObject = {
|
export type AsObject = {
|
||||||
socketPath: string,
|
socketPath: string,
|
||||||
logPath: string,
|
logPath: string,
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ var jspb = require('google-protobuf');
|
|||||||
var goog = jspb;
|
var goog = jspb;
|
||||||
var global = Function('return this')();
|
var global = Function('return this')();
|
||||||
|
|
||||||
goog.exportSymbol('proto.SharedProcessActiveMessage', null, global);
|
goog.exportSymbol('proto.SharedProcessActive', null, global);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generated by JsPbCodeGenerator.
|
* Generated by JsPbCodeGenerator.
|
||||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||||
@@ -23,15 +22,20 @@ goog.exportSymbol('proto.SharedProcessActiveMessage', null, global);
|
|||||||
* @extends {jspb.Message}
|
* @extends {jspb.Message}
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage = function(opt_data) {
|
proto.SharedProcessActive = function(opt_data) {
|
||||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||||
};
|
};
|
||||||
goog.inherits(proto.SharedProcessActiveMessage, jspb.Message);
|
goog.inherits(proto.SharedProcessActive, jspb.Message);
|
||||||
if (goog.DEBUG && !COMPILED) {
|
if (goog.DEBUG && !COMPILED) {
|
||||||
proto.SharedProcessActiveMessage.displayName = 'proto.SharedProcessActiveMessage';
|
/**
|
||||||
|
* @public
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
proto.SharedProcessActive.displayName = 'proto.SharedProcessActive';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||||
/**
|
/**
|
||||||
* Creates an object representation of this proto suitable for use in Soy templates.
|
* Creates an object representation of this proto suitable for use in Soy templates.
|
||||||
@@ -43,8 +47,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) {
|
|||||||
* for transitional soy proto support: http://goto/soy-param-migration
|
* for transitional soy proto support: http://goto/soy-param-migration
|
||||||
* @return {!Object}
|
* @return {!Object}
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage.prototype.toObject = function(opt_includeInstance) {
|
proto.SharedProcessActive.prototype.toObject = function(opt_includeInstance) {
|
||||||
return proto.SharedProcessActiveMessage.toObject(opt_includeInstance, this);
|
return proto.SharedProcessActive.toObject(opt_includeInstance, this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -53,12 +57,12 @@ proto.SharedProcessActiveMessage.prototype.toObject = function(opt_includeInstan
|
|||||||
* @param {boolean|undefined} includeInstance Whether to include the JSPB
|
* @param {boolean|undefined} includeInstance Whether to include the JSPB
|
||||||
* instance for transitional soy proto support:
|
* instance for transitional soy proto support:
|
||||||
* http://goto/soy-param-migration
|
* http://goto/soy-param-migration
|
||||||
* @param {!proto.SharedProcessActiveMessage} msg The msg instance to transform.
|
* @param {!proto.SharedProcessActive} msg The msg instance to transform.
|
||||||
* @return {!Object}
|
* @return {!Object}
|
||||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage.toObject = function(includeInstance, msg) {
|
proto.SharedProcessActive.toObject = function(includeInstance, msg) {
|
||||||
var f, obj = {
|
var obj = {
|
||||||
socketPath: jspb.Message.getFieldWithDefault(msg, 1, ""),
|
socketPath: jspb.Message.getFieldWithDefault(msg, 1, ""),
|
||||||
logPath: jspb.Message.getFieldWithDefault(msg, 2, "")
|
logPath: jspb.Message.getFieldWithDefault(msg, 2, "")
|
||||||
};
|
};
|
||||||
@@ -74,23 +78,23 @@ proto.SharedProcessActiveMessage.toObject = function(includeInstance, msg) {
|
|||||||
/**
|
/**
|
||||||
* Deserializes binary data (in protobuf wire format).
|
* Deserializes binary data (in protobuf wire format).
|
||||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||||
* @return {!proto.SharedProcessActiveMessage}
|
* @return {!proto.SharedProcessActive}
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage.deserializeBinary = function(bytes) {
|
proto.SharedProcessActive.deserializeBinary = function(bytes) {
|
||||||
var reader = new jspb.BinaryReader(bytes);
|
var reader = new jspb.BinaryReader(bytes);
|
||||||
var msg = new proto.SharedProcessActiveMessage;
|
var msg = new proto.SharedProcessActive;
|
||||||
return proto.SharedProcessActiveMessage.deserializeBinaryFromReader(msg, reader);
|
return proto.SharedProcessActive.deserializeBinaryFromReader(msg, reader);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserializes binary data (in protobuf wire format) from the
|
* Deserializes binary data (in protobuf wire format) from the
|
||||||
* given reader into the given message object.
|
* given reader into the given message object.
|
||||||
* @param {!proto.SharedProcessActiveMessage} msg The message object to deserialize into.
|
* @param {!proto.SharedProcessActive} msg The message object to deserialize into.
|
||||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||||
* @return {!proto.SharedProcessActiveMessage}
|
* @return {!proto.SharedProcessActive}
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage.deserializeBinaryFromReader = function(msg, reader) {
|
proto.SharedProcessActive.deserializeBinaryFromReader = function(msg, reader) {
|
||||||
while (reader.nextField()) {
|
while (reader.nextField()) {
|
||||||
if (reader.isEndGroup()) {
|
if (reader.isEndGroup()) {
|
||||||
break;
|
break;
|
||||||
@@ -118,9 +122,9 @@ proto.SharedProcessActiveMessage.deserializeBinaryFromReader = function(msg, rea
|
|||||||
* Serializes the message to binary data (in protobuf wire format).
|
* Serializes the message to binary data (in protobuf wire format).
|
||||||
* @return {!Uint8Array}
|
* @return {!Uint8Array}
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage.prototype.serializeBinary = function() {
|
proto.SharedProcessActive.prototype.serializeBinary = function() {
|
||||||
var writer = new jspb.BinaryWriter();
|
var writer = new jspb.BinaryWriter();
|
||||||
proto.SharedProcessActiveMessage.serializeBinaryToWriter(this, writer);
|
proto.SharedProcessActive.serializeBinaryToWriter(this, writer);
|
||||||
return writer.getResultBuffer();
|
return writer.getResultBuffer();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,11 +132,11 @@ proto.SharedProcessActiveMessage.prototype.serializeBinary = function() {
|
|||||||
/**
|
/**
|
||||||
* Serializes the given message to binary data (in protobuf wire
|
* Serializes the given message to binary data (in protobuf wire
|
||||||
* format), writing to the given BinaryWriter.
|
* format), writing to the given BinaryWriter.
|
||||||
* @param {!proto.SharedProcessActiveMessage} message
|
* @param {!proto.SharedProcessActive} message
|
||||||
* @param {!jspb.BinaryWriter} writer
|
* @param {!jspb.BinaryWriter} writer
|
||||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage.serializeBinaryToWriter = function(message, writer) {
|
proto.SharedProcessActive.serializeBinaryToWriter = function(message, writer) {
|
||||||
var f = undefined;
|
var f = undefined;
|
||||||
f = message.getSocketPath();
|
f = message.getSocketPath();
|
||||||
if (f.length > 0) {
|
if (f.length > 0) {
|
||||||
@@ -155,13 +159,13 @@ proto.SharedProcessActiveMessage.serializeBinaryToWriter = function(message, wri
|
|||||||
* optional string socket_path = 1;
|
* optional string socket_path = 1;
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage.prototype.getSocketPath = function() {
|
proto.SharedProcessActive.prototype.getSocketPath = function() {
|
||||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
|
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** @param {string} value */
|
/** @param {string} value */
|
||||||
proto.SharedProcessActiveMessage.prototype.setSocketPath = function(value) {
|
proto.SharedProcessActive.prototype.setSocketPath = function(value) {
|
||||||
jspb.Message.setProto3StringField(this, 1, value);
|
jspb.Message.setProto3StringField(this, 1, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,13 +174,13 @@ proto.SharedProcessActiveMessage.prototype.setSocketPath = function(value) {
|
|||||||
* optional string log_path = 2;
|
* optional string log_path = 2;
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
proto.SharedProcessActiveMessage.prototype.getLogPath = function() {
|
proto.SharedProcessActive.prototype.getLogPath = function() {
|
||||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
|
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** @param {string} value */
|
/** @param {string} value */
|
||||||
proto.SharedProcessActiveMessage.prototype.setLogPath = function(value) {
|
proto.SharedProcessActive.prototype.setLogPath = function(value) {
|
||||||
jspb.Message.setProto3StringField(this, 2, value);
|
jspb.Message.setProto3StringField(this, 2, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import * as path from "path";
|
|||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import { createClient } from "@coder/protocol/test";
|
import { createClient } from "@coder/protocol/test";
|
||||||
|
import { Module } from "../src/common/proxy";
|
||||||
const client = createClient();
|
|
||||||
jest.mock("../src/fill/client", () => ({ client }));
|
|
||||||
const cp = require("../src/fill/child_process") as typeof import("child_process");
|
|
||||||
|
|
||||||
describe("child_process", () => {
|
describe("child_process", () => {
|
||||||
|
const client = createClient();
|
||||||
|
const cp = client.modules[Module.ChildProcess];
|
||||||
|
|
||||||
const getStdout = async (proc: ChildProcess): Promise<string> => {
|
const getStdout = async (proc: ChildProcess): Promise<string> => {
|
||||||
return new Promise((r): Readable => proc.stdout.on("data", r))
|
return new Promise((r): Readable => proc.stdout!.once("data", r))
|
||||||
.then((s) => s.toString());
|
.then((s) => s.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,10 +36,10 @@ describe("child_process", () => {
|
|||||||
it("should cat", async () => {
|
it("should cat", async () => {
|
||||||
const proc = cp.spawn("cat", []);
|
const proc = cp.spawn("cat", []);
|
||||||
expect(proc.pid).toBe(-1);
|
expect(proc.pid).toBe(-1);
|
||||||
proc.stdin.write("banana");
|
proc.stdin!.write("banana");
|
||||||
await expect(getStdout(proc)).resolves.toBe("banana");
|
await expect(getStdout(proc)).resolves.toBe("banana");
|
||||||
|
|
||||||
proc.stdin.end();
|
proc.stdin!.end();
|
||||||
proc.kill();
|
proc.kill();
|
||||||
|
|
||||||
expect(proc.pid).toBeGreaterThan(-1);
|
expect(proc.pid).toBeGreaterThan(-1);
|
||||||
@@ -53,6 +53,11 @@ describe("child_process", () => {
|
|||||||
|
|
||||||
await expect(getStdout(proc)).resolves.toContain("hi=donkey\n");
|
await expect(getStdout(proc)).resolves.toContain("hi=donkey\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should eval", async () => {
|
||||||
|
const proc = cp.spawn("node", ["-e", "console.log('foo')"]);
|
||||||
|
await expect(getStdout(proc)).resolves.toContain("foo");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fork", () => {
|
describe("fork", () => {
|
||||||
@@ -71,4 +76,28 @@ describe("child_process", () => {
|
|||||||
await new Promise((r): ChildProcess => proc.on("exit", r));
|
await new Promise((r): ChildProcess => proc.on("exit", r));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should dispose", (done) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
client.dispose();
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should disconnect", async () => {
|
||||||
|
const client = createClient();
|
||||||
|
const cp = client.modules[Module.ChildProcess];
|
||||||
|
const proc = cp.fork(path.join(__dirname, "forker.js"));
|
||||||
|
const fn = jest.fn();
|
||||||
|
proc.on("error", fn);
|
||||||
|
|
||||||
|
proc.send({ bananas: true });
|
||||||
|
await expect(new Promise((r): ChildProcess => proc.on("message", r)))
|
||||||
|
.resolves.toMatchObject({
|
||||||
|
bananas: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
client.dispose();
|
||||||
|
expect(fn).toHaveBeenCalledWith(new Error("disconnected"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { createClient } from "./helpers";
|
|
||||||
|
|
||||||
describe("Evaluate", () => {
|
|
||||||
const client = createClient();
|
|
||||||
|
|
||||||
it("should transfer string", async () => {
|
|
||||||
const value = await client.evaluate(() => {
|
|
||||||
return "hi";
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(value).toEqual("hi");
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
it("should compute from string", async () => {
|
|
||||||
const start = "ban\%\$\"``a,,,,asdasd";
|
|
||||||
const value = await client.evaluate((_helper, a) => {
|
|
||||||
return a;
|
|
||||||
}, start);
|
|
||||||
|
|
||||||
expect(value).toEqual(start);
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
it("should compute from object", async () => {
|
|
||||||
const value = await client.evaluate((_helper, arg) => {
|
|
||||||
return arg.bananas * 2;
|
|
||||||
}, { bananas: 1 });
|
|
||||||
|
|
||||||
expect(value).toEqual(2);
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
it("should transfer object", async () => {
|
|
||||||
const value = await client.evaluate(() => {
|
|
||||||
return { alpha: "beta" };
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(value.alpha).toEqual("beta");
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
it("should require", async () => {
|
|
||||||
const value = await client.evaluate(() => {
|
|
||||||
const fs = require("fs") as typeof import("fs");
|
|
||||||
|
|
||||||
return Object.keys(fs).filter((f) => f === "readFileSync");
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(value[0]).toEqual("readFileSync");
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
it("should resolve with promise", async () => {
|
|
||||||
const value = await client.evaluate(async () => {
|
|
||||||
await new Promise((r): number => setTimeout(r, 100));
|
|
||||||
|
|
||||||
return "donkey";
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(value).toEqual("donkey");
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
it("should do active process", (done) => {
|
|
||||||
const runner = client.run((ae) => {
|
|
||||||
ae.on("first", () => {
|
|
||||||
ae.emit("first:response");
|
|
||||||
ae.on("second", () => ae.emit("second:response"));
|
|
||||||
});
|
|
||||||
|
|
||||||
const disposeCallbacks = <Array<() => void>>[];
|
|
||||||
const dispose = (): void => {
|
|
||||||
disposeCallbacks.forEach((cb) => cb());
|
|
||||||
ae.emit("disposed");
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
onDidDispose: (cb: () => void): number => disposeCallbacks.push(cb),
|
|
||||||
dispose,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
runner.emit("first");
|
|
||||||
runner.on("first:response", () => runner.emit("second"));
|
|
||||||
runner.on("second:response", () => client.dispose());
|
|
||||||
|
|
||||||
runner.on("disposed", () => done());
|
|
||||||
});
|
|
||||||
});
|
|
||||||