Skip to content

Commit b8ba287

Browse files
authored
fix: disable websocket compression for startup logs in Safari (#8087)
1 parent c3781d9 commit b8ba287

File tree

5 files changed

+48
-8
lines changed

5 files changed

+48
-8
lines changed

coderd/apidoc/docs.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/workspaceagents.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R
405405
// @Param before query int false "Before log id"
406406
// @Param after query int false "After log id"
407407
// @Param follow query bool false "Follow log stream"
408+
// @Param no_compression query bool false "Disable compression for WebSocket connection"
408409
// @Success 200 {array} codersdk.WorkspaceAgentStartupLog
409410
// @Router /workspaceagents/{workspaceagent}/startup-logs [get]
410411
func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Request) {
@@ -415,6 +416,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques
415416
logger = api.Logger.With(slog.F("workspace_agent_id", workspaceAgent.ID))
416417
follow = r.URL.Query().Has("follow")
417418
afterRaw = r.URL.Query().Get("after")
419+
noCompression = r.URL.Query().Has("no_compression")
418420
)
419421

420422
var after int64
@@ -460,7 +462,21 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques
460462
api.WebsocketWaitGroup.Add(1)
461463
api.WebsocketWaitMutex.Unlock()
462464
defer api.WebsocketWaitGroup.Done()
463-
conn, err := websocket.Accept(rw, r, nil)
465+
466+
opts := &websocket.AcceptOptions{}
467+
468+
// Allow client to request no compression. This is useful for buggy
469+
// clients or if there's a client/server incompatibility. This is
470+
// needed with e.g. nhooyr/websocket and Safari (confirmed in 16.5).
471+
//
472+
// See:
473+
// * https://github.com/nhooyr/websocket/issues/218
474+
// * https://github.com/gobwas/ws/issues/169
475+
if noCompression {
476+
opts.CompressionMode = websocket.CompressionDisabled
477+
}
478+
479+
conn, err := websocket.Accept(rw, r, opts)
464480
if err != nil {
465481
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
466482
Message: "Failed to accept websocket.",

docs/api/agents.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -689,12 +689,13 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/sta
689689

690690
### Parameters
691691

692-
| Name | In | Type | Required | Description |
693-
| ---------------- | ----- | ------------ | -------- | ------------------ |
694-
| `workspaceagent` | path | string(uuid) | true | Workspace agent ID |
695-
| `before` | query | integer | false | Before log id |
696-
| `after` | query | integer | false | After log id |
697-
| `follow` | query | boolean | false | Follow log stream |
692+
| Name | In | Type | Required | Description |
693+
| ---------------- | ----- | ------------ | -------- | -------------------------------------------- |
694+
| `workspaceagent` | path | string(uuid) | true | Workspace agent ID |
695+
| `before` | query | integer | false | Before log id |
696+
| `after` | query | integer | false | After log id |
697+
| `follow` | query | boolean | false | Follow log stream |
698+
| `no_compression` | query | boolean | false | Disable compression for WebSocket connection |
698699

699700
### Example responses
700701

site/src/api/api.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as Types from "./types"
44
import { DeploymentConfig } from "./types"
55
import * as TypesGen from "./typesGenerated"
66
import { delay } from "utils/delay"
7+
import userAgentParser from "ua-parser-js"
78

89
// Adds 304 for the default axios validateStatus function
910
// https://github.com/axios/axios#handling-errors Check status here
@@ -1231,9 +1232,19 @@ export const watchStartupLogs = (
12311232
agentId: string,
12321233
{ after, onMessage, onDone, onError }: WatchStartupLogsOptions,
12331234
) => {
1235+
// WebSocket compression in Safari (confirmed in 16.5) is broken when
1236+
// the server sends large messages. The following error is seen:
1237+
//
1238+
// WebSocket connection to 'wss://.../startup-logs?follow&after=0' failed: The operation couldn’t be completed. Protocol error
1239+
//
1240+
const noCompression =
1241+
userAgentParser(navigator.userAgent).browser.name === "Safari"
1242+
? "&no_compression"
1243+
: ""
1244+
12341245
const proto = location.protocol === "https:" ? "wss:" : "ws:"
12351246
const socket = new WebSocket(
1236-
`${proto}//${location.host}/api/v2/workspaceagents/${agentId}/startup-logs?follow&after=${after}`,
1247+
`${proto}//${location.host}/api/v2/workspaceagents/${agentId}/startup-logs?follow&after=${after}${noCompression}`,
12371248
)
12381249
socket.binaryType = "blob"
12391250
socket.addEventListener("message", (event) => {

0 commit comments

Comments
 (0)