|  | 
|  | 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 | +} | 
0 commit comments