Skip to content

attempt to reconnect up to twice in the background before displaying the reconnection pop-up #2283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,17 @@ We also have an in-depth [setup and configuration](./doc/guide.md) guide.

### Alpha Program 🐣

We're working on a cloud platform that makes deploying and managing code-server easier. Consider [joining our alpha program](https://codercom.typeform.com/to/U4IKyv0W) if you don't want to worry about
We're working on a cloud platform that makes deploying and managing code-server easier. Consider [updating to 3.6.2](https://github.com/cdr/code-server/releases/tag/v3.6.2) and running code-server with our experimental flag `--link` if you don't want to worry about

- TLS
- Authentication
- Port Forwarding

```bash
$ code-server --link
Proxying code-server to Coder Cloud, you can access your IDE at https://valmar-jon.cdr.co
```

## FAQ

See [./doc/FAQ.md](./doc/FAQ.md).
Expand Down
1 change: 1 addition & 0 deletions ci/dev/fmt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ main() {
doctoc --title '# Install' doc/install.md > /dev/null
doctoc --title '# npm Install Requirements' doc/npm.md > /dev/null
doctoc --title '# Contributing' doc/CONTRIBUTING.md > /dev/null
doctoc --title '# iPad' doc/ipad.md > /dev/null

if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then
echo "Files need generation or are formatted incorrectly:"
Expand Down
49 changes: 48 additions & 1 deletion ci/dev/vscode.patch
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ index 3715cbb8e6ee41c3d9b5090918d243b723ae2d00..c65de8ad37e727d66da97a8f8b170cbc
-
-
diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts
index 18d3d04fd20335975293e37b3b641120dd92da20..4e49f9d63623da6c84624144765f76ec127ea526 100644
index 18d3d04fd20335975293e37b3b641120dd92da20..e072fc38ccd950462f42fdf112c8e7e673f351f5 100644
--- a/src/vs/platform/remote/common/remoteAgentConnection.ts
+++ b/src/vs/platform/remote/common/remoteAgentConnection.ts
@@ -92,7 +92,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
Expand All @@ -689,6 +689,53 @@ index 18d3d04fd20335975293e37b3b641120dd92da20..4e49f9d63623da6c84624144765f76ec
(err: any, socket: ISocket | undefined) => {
if (err || !socket) {
options.logService.error(`${logPrefix} socketFactory.connect() failed. Error:`);
@@ -411,16 +411,23 @@ abstract class PersistentConnection extends Disposable {
}
const logPrefix = commonLogPrefix(this._connectionType, this.reconnectionToken, true);
this._options.logService.info(`${logPrefix} starting reconnecting loop. You can get more information with the trace log level.`);
- this._onDidStateChange.fire(new ConnectionLostEvent());
const TIMES = [5, 5, 10, 10, 10, 10, 10, 30];
+ const SHOW_POPUP_ON_ATTEMPT = 2 // aka third attempt
+
const disconnectStartTime = Date.now();
let attempt = -1;
do {
attempt++;
+ if (attempt == SHOW_POPUP_ON_ATTEMPT){
+ this._onDidStateChange.fire(new ConnectionLostEvent());
+ }
+
const waitTime = (attempt < TIMES.length ? TIMES[attempt] : TIMES[TIMES.length - 1]);
try {
const sleepPromise = sleep(waitTime);
- this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime, sleepPromise));
+ if (attempt >= SHOW_POPUP_ON_ATTEMPT) {
+ this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime, sleepPromise));
+ }

this._options.logService.info(`${logPrefix} waiting for ${waitTime} seconds before reconnecting...`);
try {
@@ -433,14 +440,17 @@ abstract class PersistentConnection extends Disposable {
}

// connection was lost, let's try to re-establish it
- this._onDidStateChange.fire(new ReconnectionRunningEvent());
+ if (attempt >= SHOW_POPUP_ON_ATTEMPT){
+ this._onDidStateChange.fire(new ReconnectionRunningEvent());
+ }
this._options.logService.info(`${logPrefix} resolving connection...`);
const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol);
this._options.logService.info(`${logPrefix} connecting to ${simpleOptions.host}:${simpleOptions.port}...`);
await connectWithTimeLimit(simpleOptions.logService, this._reconnect(simpleOptions), RECONNECT_TIMEOUT);
this._options.logService.info(`${logPrefix} reconnected!`);
- this._onDidStateChange.fire(new ConnectionGainEvent());
-
+ if (attempt >= SHOW_POPUP_ON_ATTEMPT) {
+ this._onDidStateChange.fire(new ConnectionGainEvent());
+ }
break;
} catch (err) {
if (err.code === 'VSCODE_CONNECTION_ERROR') {
diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts
index ab3fd347b69f8a3d9b96e706cd87c911b8ffed6b..9d351037b577f9f1edfd18ae9b3c48a211f4467f 100644
--- a/src/vs/platform/storage/browser/storageService.ts
Expand Down
18 changes: 8 additions & 10 deletions doc/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# FAQ

- [Questions?](#questions)
- [iPad Status?](#ipad-status)
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
- [Differences compared to VS Code?](#differences-compared-to-vs-code)
- [How can I request a missing extension?](#how-can-i-request-a-missing-extension)
Expand All @@ -21,7 +22,6 @@
- [Heartbeat File](#heartbeat-file)
- [Healthz endpoint](#healthz-endpoint)
- [How does the config file work?](#how-does-the-config-file-work)
- [Blank screen on iPad?](#blank-screen-on-ipad)
- [Isn't an install script piped into sh insecure?](#isnt-an-install-script-piped-into-sh-insecure)
- [How do I make my keyboard shortcuts work?](#how-do-i-make-my-keyboard-shortcuts-work)
- [Differences compared to Theia?](#differences-compared-to-theia)
Expand All @@ -33,6 +33,10 @@

Please file all questions and support requests at https://github.com/cdr/code-server/discussions.

## iPad Status?

Please see [./ipad.md](./ipad.md).

## How can I reuse my VS Code configuration?

The very popular [Settings Sync](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync) extension works.
Expand Down Expand Up @@ -144,6 +148,9 @@ For HTTPS, you can use a self signed certificate by passing in just `--cert` or
pass in an existing certificate by providing the path to `--cert` and the path to
the key with `--cert-key`.

The self signed certificate will be generated into
`~/.local/share/code-server/self-signed.crt`.

If `code-server` has been passed a certificate it will also respond to HTTPS
requests and will redirect all HTTP requests to HTTPS.

Expand Down Expand Up @@ -279,15 +286,6 @@ The `--config` flag or `$CODE_SERVER_CONFIG` can be used to change the config fi

The default location also respects `$XDG_CONFIG_HOME`.

## Blank screen on iPad?

Unfortunately at the moment self signed certificates cause a blank screen on iPadOS

There does seem to be a way to get it to work if you create your own CA and create a
certificate using the CA and then import the CA onto your iPad.

See [#1566](https://github.com/cdr/code-server/issues/1566#issuecomment-623159434).

## Isn't an install script piped into sh insecure?

Please give
Expand Down
3 changes: 1 addition & 2 deletions doc/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,7 @@ Visit `https://<your-domain-name>` to access `code-server`. Congratulations!

### Self Signed Certificate

**note:** Self signed certificates do not work with iPad and will cause a blank page. You'll
have to use [Let's Encrypt](#lets-encrypt) instead. See the [FAQ](./FAQ.md#blank-screen-on-ipad).
**note:** Self signed certificates do not work with iPad normally. See [./ipad.md](./ipad.md) for details.

Recommended reading: https://security.stackexchange.com/a/8112.

Expand Down
56 changes: 56 additions & 0 deletions doc/ipad.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# iPad

- [iPad](#ipad)
- [Known Issues](#known-issues)
- [How to access code-server with a self signed certificate on iPad?](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

# iPad

## Known Issues

- Getting self signed certificates certificates to work is involved, see below.
- Keyboard may disappear sometimes [#1313](https://github.com/cdr/code-server/issues/1313), [#979](https://github.com/cdr/code-server/issues/979)
- Trackpad scrolling does not work [#1455](https://github.com/cdr/code-server/issues/1455)
- See [issues tagged with the iPad label](https://github.com/cdr/code-server/issues?q=is%3Aopen+is%3Aissue+label%3AiPad) for more.

## How to access code-server with a self signed certificate on iPad?

Accessing a self signed certificate on iPad isn't as easy as accepting through all
the security warnings. Safari will prevent WebSocket connections unless the certificate
is installed as a profile on the device.

The below assumes you are using the self signed certificate that code-server
generates for you. If not, that's fine but you'll have to make sure your certificate
abides by the following guidelines from Apple: https://support.apple.com/en-us/HT210176

**note**: Another undocumented requirement we noticed is that the certificate has to have `basicConstraints=CA:true`.

The following instructions assume you have code-server installed and running
with a self signed certificate. If not, please first go through [./guide.md](./guide.md)!

**warning**: Your iPad must access code-server via a domain name. It could be local
DNS like `mymacbookpro.local` but it must be a domain name. Otherwise Safari will
refuse to allow WebSockets to connect.

1. Your certificate **must** have a subject alt name that matches the hostname
at which you will access code-server from your iPad. You can pass this to code-server
so that it generates the certificate correctly with `--cert-host`.
2. Share your self signed certificate with the iPad.
- code-server will print the location of the certificate it has generated in the logs.

```
[2020-10-30T08:55:45.139Z] info - Using generated certificate and key for HTTPS: ~/.local/share/code-server/mymbp_local.crt
```

- You can mail it to yourself or if you have a Mac, it's easiest to just Airdrop to the iPad.

3. When opening the `*.crt` file, you'll be prompted to go into settings to install.
4. Go to `Settings -> General -> Profile`, select the profile and then hit `Install`.
- It should say the profile is verified.
5. Go to `Settings -> About -> Certificate Trust Settings` and enable full trust for
the certificate.
6. Now you can access code-server! 🍻
7 changes: 6 additions & 1 deletion src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface Args extends VsArgs {
readonly auth?: AuthType
readonly password?: string
readonly cert?: OptionalString
readonly "cert-host"?: string
readonly "cert-key"?: string
readonly "disable-telemetry"?: boolean
readonly help?: boolean
Expand Down Expand Up @@ -101,7 +102,11 @@ const options: Options<Required<Args>> = {
cert: {
type: OptionalString,
path: true,
description: "Path to certificate. Generated if no path is provided.",
description: "Path to certificate. A self signed certificate is generated if none is provided.",
},
"cert-host": {
type: "string",
description: "Hostname to use when generating a self signed certificate.",
},
"cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." },
"disable-telemetry": { type: "boolean", description: "Disable telemetry." },
Expand Down
4 changes: 2 additions & 2 deletions src/node/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ const main = async (args: Args, configArgs: Args): Promise<void> => {
proxyDomains: args["proxy-domain"],
socket: args.socket,
...(args.cert && !args.cert.value
? await generateCertificate()
? await generateCertificate(args["cert-host"] || "localhost")
: {
cert: args.cert && args.cert.value,
certKey: args["cert-key"],
Expand Down Expand Up @@ -209,7 +209,7 @@ const main = async (args: Args, configArgs: Args): Promise<void> => {
logger.info(
args.cert && args.cert.value
? ` - Using provided certificate and key for HTTPS`
: ` - Using generated certificate and key for HTTPS`,
: ` - Using generated certificate and key for HTTPS: ${humanPath(options.cert)}`,
)
} else {
logger.info(" - Not serving HTTPS")
Expand Down
44 changes: 32 additions & 12 deletions src/node/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,45 @@ export function humanPath(p?: string): string {
return p.replace(os.homedir(), "~")
}

export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => {
const paths = {
cert: path.join(tmpdir, "self-signed.cert"),
certKey: path.join(tmpdir, "self-signed.key"),
}
const checks = await Promise.all([fs.pathExists(paths.cert), fs.pathExists(paths.certKey)])
export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => {
const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt`)
const certKeyPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`)

const checks = await Promise.all([fs.pathExists(certPath), fs.pathExists(certKeyPath)])
if (!checks[0] || !checks[1]) {
// Require on demand so openssl isn't required if you aren't going to
// generate certificates.
const pem = require("pem") as typeof import("pem")
const certs = await new Promise<import("pem").CertificateCreationResult>((resolve, reject): void => {
pem.createCertificate({ selfSigned: true }, (error, result) => {
return error ? reject(error) : resolve(result)
})
pem.createCertificate(
{
selfSigned: true,
commonName: hostname,
config: `
[req]
req_extensions = v3_req

[ v3_req ]
basicConstraints = CA:true
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${hostname}
`,
},
(error, result) => {
return error ? reject(error) : resolve(result)
},
)
})
await fs.mkdirp(tmpdir)
await Promise.all([fs.writeFile(paths.cert, certs.certificate), fs.writeFile(paths.certKey, certs.serviceKey)])
await fs.mkdirp(paths.data)
await Promise.all([fs.writeFile(certPath, certs.certificate), fs.writeFile(certKeyPath, certs.serviceKey)])
}
return {
cert: certPath,
certKey: certKeyPath,
}
return paths
}

export const generatePassword = async (length = 24): Promise<string> => {
Expand Down
2 changes: 1 addition & 1 deletion test/socket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe("SocketProxyProvider", () => {
}

before(async () => {
const cert = await generateCertificate()
const cert = await generateCertificate("localhost")
const options = {
cert: fs.readFileSync(cert.cert),
key: fs.readFileSync(cert.certKey),
Expand Down