Skip to content

Commit 9f918b8

Browse files
feat: add support for coder inbox (#444)
1 parent a356a31 commit 9f918b8

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

src/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function needToken(): boolean {
2828
/**
2929
* Create a new agent based off the current settings.
3030
*/
31-
async function createHttpAgent(): Promise<ProxyAgent> {
31+
export async function createHttpAgent(): Promise<ProxyAgent> {
3232
const cfg = vscode.workspace.getConfiguration()
3333
const insecure = Boolean(cfg.get("coder.insecure"))
3434
const certFile = expandPath(String(cfg.get("coder.tlsCertFile") ?? "").trim())

src/inbox.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { Api } from "coder/site/src/api/api"
2+
import { Workspace, GetInboxNotificationResponse } from "coder/site/src/api/typesGenerated"
3+
import { ProxyAgent } from "proxy-agent"
4+
import * as vscode from "vscode"
5+
import { WebSocket } from "ws"
6+
import { errToStr } from "./api-helper"
7+
import { type Storage } from "./storage"
8+
9+
// These are the template IDs of our notifications.
10+
// Maybe in the future we should avoid hardcoding
11+
// these in both coderd and here.
12+
const TEMPLATE_WORKSPACE_OUT_OF_MEMORY = "a9d027b4-ac49-4fb1-9f6d-45af15f64e7a"
13+
const TEMPLATE_WORKSPACE_OUT_OF_DISK = "f047f6a3-5713-40f7-85aa-0394cce9fa3a"
14+
15+
export class Inbox implements vscode.Disposable {
16+
readonly #storage: Storage
17+
#disposed = false
18+
#socket: WebSocket
19+
20+
constructor(workspace: Workspace, httpAgent: ProxyAgent, restClient: Api, storage: Storage) {
21+
this.#storage = storage
22+
23+
const baseUrlRaw = restClient.getAxiosInstance().defaults.baseURL
24+
if (!baseUrlRaw) {
25+
throw new Error("No base URL set on REST client")
26+
}
27+
28+
const watchTemplates = [TEMPLATE_WORKSPACE_OUT_OF_DISK, TEMPLATE_WORKSPACE_OUT_OF_MEMORY]
29+
const watchTemplatesParam = encodeURIComponent(watchTemplates.join(","))
30+
31+
const watchTargets = [workspace.id]
32+
const watchTargetsParam = encodeURIComponent(watchTargets.join(","))
33+
34+
// We shouldn't need to worry about this throwing. Whilst `baseURL` could
35+
// be an invalid URL, that would've caused issues before we got to here.
36+
const baseUrl = new URL(baseUrlRaw)
37+
const socketProto = baseUrl.protocol === "https:" ? "wss:" : "ws:"
38+
const socketUrl = `${socketProto}//${baseUrl.host}/api/v2/notifications/inbox/watch?format=plaintext&templates=${watchTemplatesParam}&targets=${watchTargetsParam}`
39+
40+
const coderSessionTokenHeader = "Coder-Session-Token"
41+
this.#socket = new WebSocket(new URL(socketUrl), {
42+
followRedirects: true,
43+
agent: httpAgent,
44+
headers: {
45+
[coderSessionTokenHeader]: restClient.getAxiosInstance().defaults.headers.common[coderSessionTokenHeader] as
46+
| string
47+
| undefined,
48+
},
49+
})
50+
51+
this.#socket.on("open", () => {
52+
this.#storage.writeToCoderOutputChannel("Listening to Coder Inbox")
53+
})
54+
55+
this.#socket.on("error", (error) => {
56+
this.notifyError(error)
57+
this.dispose()
58+
})
59+
60+
this.#socket.on("message", (data) => {
61+
try {
62+
const inboxMessage = JSON.parse(data.toString()) as GetInboxNotificationResponse
63+
64+
vscode.window.showInformationMessage(inboxMessage.notification.title)
65+
} catch (error) {
66+
this.notifyError(error)
67+
}
68+
})
69+
}
70+
71+
dispose() {
72+
if (!this.#disposed) {
73+
this.#storage.writeToCoderOutputChannel("No longer listening to Coder Inbox")
74+
this.#socket.close()
75+
this.#disposed = true
76+
}
77+
}
78+
79+
private notifyError(error: unknown) {
80+
const message = errToStr(error, "Got empty error while monitoring Coder Inbox")
81+
this.#storage.writeToCoderOutputChannel(message)
82+
}
83+
}

src/remote.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import * as path from "path"
99
import prettyBytes from "pretty-bytes"
1010
import * as semver from "semver"
1111
import * as vscode from "vscode"
12-
import { makeCoderSdk, needToken, startWorkspaceIfStoppedOrFailed, waitForBuild } from "./api"
12+
import { createHttpAgent, makeCoderSdk, needToken, startWorkspaceIfStoppedOrFailed, waitForBuild } from "./api"
1313
import { extractAgents } from "./api-helper"
1414
import * as cli from "./cliManager"
1515
import { Commands } from "./commands"
1616
import { featureSetForVersion, FeatureSet } from "./featureSet"
1717
import { getHeaderCommand } from "./headers"
18+
import { Inbox } from "./inbox"
1819
import { SSHConfig, SSHValues, mergeSSHConfigValues } from "./sshConfig"
1920
import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport"
2021
import { Storage } from "./storage"
@@ -403,6 +404,11 @@ export class Remote {
403404
disposables.push(monitor)
404405
disposables.push(monitor.onChange.event((w) => (this.commands.workspace = w)))
405406

407+
// Watch coder inbox for messages
408+
const httpAgent = await createHttpAgent()
409+
const inbox = new Inbox(workspace, httpAgent, workspaceRestClient, this.storage)
410+
disposables.push(inbox)
411+
406412
// Wait for the agent to connect.
407413
if (agent.status === "connecting") {
408414
this.storage.writeToCoderOutputChannel(`Waiting for ${workspaceName}/${agent.name}...`)

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1594,7 +1594,7 @@ co@3.1.0:
15941594

15951595
"coder@https://github.com/coder/coder#main":
15961596
version "0.0.0"
1597-
resolved "https://github.com/coder/coder#975ea23d6f49a4043131f79036d1bf5166eb9140"
1597+
resolved "https://github.com/coder/coder#3ac844ad3d341d2910542b83d4f33df7bd0be85e"
15981598

15991599
collapse-white-space@^1.0.2:
16001600
version "1.0.6"

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