diff --git a/app/client/models/AdminChecks.ts b/app/client/models/AdminChecks.ts
index f8ee5420213..a0f334ae92f 100644
--- a/app/client/models/AdminChecks.ts
+++ b/app/client/models/AdminChecks.ts
@@ -162,6 +162,16 @@ It is good practice not to run Grist as the root user.
'reachable': {
info: `
The main page of Grist should be available.
+`
+ },
+
+ 'websockets': {
+ 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. See this
+documentation for more explanation.
`
},
};
diff --git a/app/common/BootProbe.ts b/app/common/BootProbe.ts
index 753af1c4b48..699d3515659 100644
--- a/app/common/BootProbe.ts
+++ b/app/common/BootProbe.ts
@@ -6,7 +6,8 @@ export type BootProbeIds =
'reachable' |
'host-header' |
'sandboxing' |
- 'system-user'
+ 'system-user' |
+ 'websockets'
;
export interface BootProbeResult {
diff --git a/app/server/lib/BootProbes.ts b/app/server/lib/BootProbes.ts
index 8e3ae366acd..ba928ce3ff0 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';
/**
@@ -58,6 +59,7 @@ export class BootProbes {
this._probes.push(_bootProbe);
this._probes.push(_hostHeaderProbe);
this._probes.push(_sandboxingProbe);
+ this._probes.push(_webSocketsProbe);
this._probeById = new Map(this._probes.map(p => [p.id, p]));
}
}
@@ -104,6 +106,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',