diff --git a/app/client/models/AdminChecks.ts b/app/client/models/AdminChecks.ts index 51ff8b2bd7..bd5a8cee49 100644 --- a/app/client/models/AdminChecks.ts +++ b/app/client/models/AdminChecks.ts @@ -163,6 +163,15 @@ It is good practice not to run Grist as the root user. 'reachable': { info: ` The main page of Grist should be available. +` + }, + + 'websockets': { + // TODO: add a link to https://support.getgrist.com/self-managed/#how-do-i-run-grist-on-a-server + info: ` +Websocket connections need HTTP 1.1 and the ability to pass a few +extra headers in order to work. Sometimes a reverse proxy can +interfere with these requirements. ` }, }; diff --git a/app/common/BootProbe.ts b/app/common/BootProbe.ts index dd86704937..0da3c859fc 100644 --- a/app/common/BootProbe.ts +++ b/app/common/BootProbe.ts @@ -7,7 +7,8 @@ export type BootProbeIds = 'host-header' | 'sandboxing' | 'system-user' | - 'authentication' + 'authentication' | + 'websockets' ; export interface BootProbeResult { diff --git a/app/server/lib/BootProbes.ts b/app/server/lib/BootProbes.ts index b1186c51a4..8145f2a2ce 100644 --- a/app/server/lib/BootProbes.ts +++ b/app/server/lib/BootProbes.ts @@ -4,6 +4,7 @@ import { removeTrailingSlash } from 'app/common/gutil'; import { expressWrap, jsonErrorHandler } from 'app/server/lib/expressWrap'; import { GristServer } from 'app/server/lib/GristServer'; import * as express from 'express'; +import WS from 'ws'; import fetch from 'node-fetch'; /** @@ -59,6 +60,7 @@ export class BootProbes { this._probes.push(_hostHeaderProbe); this._probes.push(_sandboxingProbe); this._probes.push(_authenticationProbe); + this._probes.push(_webSocketsProbe); this._probeById = new Map(this._probes.map(p => [p.id, p])); } } @@ -105,6 +107,37 @@ const _homeUrlReachableProbe: Probe = { } }; +const _webSocketsProbe: Probe = { + id: 'websockets', + name: 'Can we open a websocket with the server', + apply: async (server, req) => { + return new Promise((resolve) => { + const url = new URL(server.getHomeUrl(req)); + url.protocol = (url.protocol === 'https:') ? 'wss:' : 'ws:'; + const ws = new WS.WebSocket(url.href); + const details: Record = { + url, + }; + ws.on('open', () => { + ws.send('Just nod if you can hear me.'); + resolve({ + success: true, + details, + }); + ws.close(); + }); + ws.on('error', (ev) => { + details.error = ev.message; + resolve({ + success: false, + details, + }); + ws.close(); + }); + }); + } +}; + const _statusCheckProbe: Probe = { id: 'health-check', name: 'Is an internal health check passing', diff --git a/package.json b/package.json index 6968344356..bbe1c5e588 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "repository": "git://github.com/gristlabs/grist-core.git", "scripts": { "start": "sandbox/watch.sh", - "start:debug": "NODE_INSPECT=1 sandbox/watch.sh", + "start:debug": "NODE_INSPECT=--inspect sandbox/watch.sh", + "start:debug-brk": "NODE_INSPECT=--inspect-brk sandbox/watch.sh", "install:python": "buildtools/prepare_python.sh", "install:python2": "buildtools/prepare_python2.sh", "install:python3": "buildtools/prepare_python3.sh", diff --git a/sandbox/watch.sh b/sandbox/watch.sh index 6cb7a750ac..531c97f62c 100755 --- a/sandbox/watch.sh +++ b/sandbox/watch.sh @@ -19,6 +19,6 @@ tsc --build -w --preserveWatchOutput $PROJECT & css_files="app/client/**/*.css" chokidar "${css_files}" -c "bash -O globstar -c 'cat ${css_files} > static/bundle.css'" & webpack --config $WEBPACK_CONFIG --mode development --watch & -NODE_PATH=_build:_build/stubs:_build/ext nodemon ${NODE_INSPECT:+--inspect} --delay 1 -w _build/app/server -w _build/app/common _build/stubs/app/server/server.js & +NODE_PATH=_build:_build/stubs:_build/ext nodemon ${NODE_INSPECT} --delay 1 -w _build/app/server -w _build/app/common _build/stubs/app/server/server.js & wait