Skip to content

Commit 56ce33c

Browse files
committed
Use Axios client for EventSource
This allows client TLS certificates to be used for event monitoring. Upgrade to a newer `eventsource` package that supports a custom `fetch` function, and provide a custom `fetch` function which wraps Axios.
1 parent d6b798e commit 56ce33c

File tree

7 files changed

+96
-30
lines changed

7 files changed

+96
-30
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22

33
## Unreleased
44

5-
## [v1.4.1](https://github.com/coder/vscode-coder/releases/tag/v1.3.9) (2025-02-19)
5+
- Use Axios client to receive event stream so TLS settings are properly applied.
66

77
### Fixed
88

9+
## [v1.4.1](https://github.com/coder/vscode-coder/releases/tag/v1.4.1) (2025-02-19)
10+
911
- Recreate REST client in spots where confirmStart may have waited indefinitely.
1012

11-
## [v1.4.0](https://github.com/coder/vscode-coder/releases/tag/v1.3.9) (2025-02-04)
13+
## [v1.4.0](https://github.com/coder/vscode-coder/releases/tag/v1.4.0) (2025-02-04)
1214

1315
- Recreate REST client after starting a workspace to ensure fresh TLS certificates.
1416
- Use `coder ssh` subcommand in place of `coder vscodessh`.
1517

16-
## [v1.3.10](https://github.com/coder/vscode-coder/releases/tag/v1.3.9) (2025-01-17)
18+
## [v1.3.10](https://github.com/coder/vscode-coder/releases/tag/v1.3.10) (2025-01-17)
1719

1820
- Fix bug where checking for overridden properties incorrectly converted host name pattern to regular expression.
1921

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,10 @@
208208
],
209209
"menus": {
210210
"commandPalette": [
211-
{
212-
"command": "coder.openFromSidebar",
213-
"when": "false"
214-
}
211+
{
212+
"command": "coder.openFromSidebar",
213+
"when": "false"
214+
}
215215
],
216216
"view/title": [
217217
{
@@ -275,7 +275,7 @@
275275
"test:ci": "CI=true yarn test"
276276
},
277277
"devDependencies": {
278-
"@types/eventsource": "^1.1.15",
278+
"@types/eventsource": "^3.0.0",
279279
"@types/glob": "^7.1.3",
280280
"@types/node": "^18.0.0",
281281
"@types/node-forge": "^1.3.11",
@@ -309,7 +309,7 @@
309309
"dependencies": {
310310
"axios": "1.7.7",
311311
"date-fns": "^3.6.0",
312-
"eventsource": "^2.0.2",
312+
"eventsource": "^3.0.5",
313313
"find-process": "^1.4.7",
314314
"jsonc-parser": "^3.3.1",
315315
"memfs": "^4.9.3",

src/api-helper.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { isApiError, isApiErrorResponse } from "coder/site/src/api/errors"
22
import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated"
3+
import { ErrorEvent } from "eventsource"
34
import { z } from "zod"
45

56
export function errToStr(error: unknown, def: string) {
@@ -9,6 +10,8 @@ export function errToStr(error: unknown, def: string) {
910
return error.response.data.message
1011
} else if (isApiErrorResponse(error)) {
1112
return error.message
13+
} else if (error instanceof ErrorEvent) {
14+
return error.code ? `${error.code}: ${error.message}` : error.message?.toString() || def
1215
} else if (typeof error === "string" && error.trim().length > 0) {
1316
return error
1417
}

src/api.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { AxiosInstance } from "axios"
12
import { spawn } from "child_process"
23
import { Api } from "coder/site/src/api/api"
34
import { ProvisionerJobLog, Workspace } from "coder/site/src/api/typesGenerated"
5+
import { FetchLikeInit } from "eventsource"
46
import fs from "fs/promises"
57
import { ProxyAgent } from "proxy-agent"
68
import * as vscode from "vscode"
@@ -120,6 +122,60 @@ export async function makeCoderSdk(baseUrl: string, token: string | undefined, s
120122
return restClient
121123
}
122124

125+
/**
126+
* Creates a fetch adapter using an Axios instance that returns streaming responses.
127+
* This can be used with APIs that accept fetch-like interfaces.
128+
*/
129+
export function createStreamingFetchAdapter(axiosInstance: AxiosInstance) {
130+
return async (url: string | URL, init?: FetchLikeInit) => {
131+
const urlStr = url.toString()
132+
133+
const response = await axiosInstance.request({
134+
url: urlStr,
135+
headers: init?.headers as Record<string, string>,
136+
responseType: "stream",
137+
validateStatus: () => true, // Don't throw on any status code
138+
})
139+
const stream = new ReadableStream({
140+
start(controller) {
141+
response.data.on("data", (chunk: Buffer) => {
142+
controller.enqueue(chunk)
143+
})
144+
145+
response.data.on("end", () => {
146+
controller.close()
147+
})
148+
149+
response.data.on("error", (err: Error) => {
150+
controller.error(err)
151+
})
152+
},
153+
154+
cancel() {
155+
response.data.destroy()
156+
return Promise.resolve()
157+
},
158+
})
159+
160+
const createReader = () => stream.getReader()
161+
162+
return {
163+
body: {
164+
getReader: () => createReader(),
165+
},
166+
url: urlStr,
167+
status: response.status,
168+
redirected: response.request.res.responseUrl !== urlStr,
169+
headers: {
170+
get: (name: string) => {
171+
const value = response.headers[name.toLowerCase()]
172+
return value === undefined ? null : String(value)
173+
},
174+
},
175+
}
176+
}
177+
}
178+
123179
/**
124180
* Start or update a workspace and return the updated workspace.
125181
*/
@@ -212,6 +268,7 @@ export async function waitForBuild(
212268
path += `&after=${logs[logs.length - 1].id}`
213269
}
214270

271+
const agent = await createHttpAgent()
215272
await new Promise<void>((resolve, reject) => {
216273
try {
217274
const baseUrl = new URL(baseUrlRaw)
@@ -224,6 +281,7 @@ export async function waitForBuild(
224281
| undefined,
225282
},
226283
followRedirects: true,
284+
agent: agent,
227285
})
228286
socket.binaryType = "nodebuffer"
229287
socket.on("message", (data) => {

src/workspaceMonitor.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Api } from "coder/site/src/api/api"
22
import { Workspace } from "coder/site/src/api/typesGenerated"
33
import { formatDistanceToNowStrict } from "date-fns"
4-
import EventSource from "eventsource"
4+
import { EventSource } from "eventsource"
55
import * as vscode from "vscode"
6+
import { createStreamingFetchAdapter } from "./api"
67
import { errToStr } from "./api-helper"
78
import { Storage } from "./storage"
89

@@ -40,16 +41,11 @@ export class WorkspaceMonitor implements vscode.Disposable {
4041
) {
4142
this.name = `${workspace.owner_name}/${workspace.name}`
4243
const url = this.restClient.getAxiosInstance().defaults.baseURL
43-
const token = this.restClient.getAxiosInstance().defaults.headers.common["Coder-Session-Token"] as
44-
| string
45-
| undefined
4644
const watchUrl = new URL(`${url}/api/v2/workspaces/${workspace.id}/watch`)
4745
this.storage.writeToCoderOutputChannel(`Monitoring ${this.name}...`)
4846

4947
const eventSource = new EventSource(watchUrl.toString(), {
50-
headers: {
51-
"Coder-Session-Token": token,
52-
},
48+
fetch: createStreamingFetchAdapter(this.restClient.getAxiosInstance()),
5349
})
5450

5551
eventSource.addEventListener("data", (event) => {
@@ -64,7 +60,7 @@ export class WorkspaceMonitor implements vscode.Disposable {
6460
})
6561

6662
eventSource.addEventListener("error", (event) => {
67-
this.notifyError(event.data)
63+
this.notifyError(event)
6864
})
6965

7066
// Store so we can close in dispose().

src/workspacesProvider.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Api } from "coder/site/src/api/api"
22
import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated"
3-
import EventSource from "eventsource"
3+
import { EventSource } from "eventsource"
44
import * as path from "path"
55
import * as vscode from "vscode"
6+
import { createStreamingFetchAdapter } from "./api"
67
import {
78
AgentMetadataEvent,
89
AgentMetadataEventSchemaArray,
@@ -228,12 +229,9 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
228229
function monitorMetadata(agentId: WorkspaceAgent["id"], restClient: Api): AgentWatcher {
229230
// TODO: Is there a better way to grab the url and token?
230231
const url = restClient.getAxiosInstance().defaults.baseURL
231-
const token = restClient.getAxiosInstance().defaults.headers.common["Coder-Session-Token"] as string | undefined
232232
const metadataUrl = new URL(`${url}/api/v2/workspaceagents/${agentId}/watch-metadata`)
233233
const eventSource = new EventSource(metadataUrl.toString(), {
234-
headers: {
235-
"Coder-Session-Token": token,
236-
},
234+
fetch: createStreamingFetchAdapter(restClient.getAxiosInstance()),
237235
})
238236

239237
let disposed = false

yarn.lock

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -537,10 +537,12 @@
537537
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
538538
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
539539

540-
"@types/eventsource@^1.1.15":
541-
version "1.1.15"
542-
resolved "https://registry.yarnpkg.com/@types/eventsource/-/eventsource-1.1.15.tgz#949383d3482e20557cbecbf3b038368d94b6be27"
543-
integrity sha512-XQmGcbnxUNa06HR3VBVkc9+A2Vpi9ZyLJcdS5dwaQQ/4ZMWFO+5c90FnMUpbtMZwB/FChoYHwuVg8TvkECacTA==
540+
"@types/eventsource@^3.0.0":
541+
version "3.0.0"
542+
resolved "https://registry.yarnpkg.com/@types/eventsource/-/eventsource-3.0.0.tgz#6b1b50c677032fd3be0b5c322e8ae819b3df62eb"
543+
integrity sha512-yEhFj31FTD29DtNeqePu+A+lD6loRef6YOM5XfN1kUwBHyy2DySGlA3jJU+FbQSkrfmlBVluf2Dub/OyReFGKA==
544+
dependencies:
545+
eventsource "*"
544546

545547
"@types/glob@^7.1.3":
546548
version "7.2.0"
@@ -2529,10 +2531,17 @@ events@^3.2.0:
25292531
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
25302532
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
25312533

2532-
eventsource@^2.0.2:
2533-
version "2.0.2"
2534-
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508"
2535-
integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==
2534+
eventsource-parser@^3.0.0:
2535+
version "3.0.0"
2536+
resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.0.tgz#9303e303ef807d279ee210a17ce80f16300d9f57"
2537+
integrity sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==
2538+
2539+
eventsource@*, eventsource@^3.0.5:
2540+
version "3.0.5"
2541+
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.5.tgz#0cae1eee2d2c75894de8b02a91d84e5c57f0cc5a"
2542+
integrity sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==
2543+
dependencies:
2544+
eventsource-parser "^3.0.0"
25362545

25372546
exec@^0.2.1:
25382547
version "0.2.1"

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy