Skip to content

Commit f2538ed

Browse files
committed
Pass the header command output to WebSocket creation
1 parent 2cd05a3 commit f2538ed

File tree

8 files changed

+143
-81
lines changed

8 files changed

+143
-81
lines changed

src/agentMetadataHelper.ts renamed to src/api/agentMetadataHelper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
type AgentMetadataEvent,
66
AgentMetadataEventSchemaArray,
77
errToStr,
8-
} from "./api/api-helper";
9-
import { type CoderApi } from "./api/coderApi";
8+
} from "./api-helper";
9+
import { type CoderApi } from "./coderApi";
1010

1111
export type AgentMetadataWatcher = {
1212
onChange: vscode.EventEmitter<null>["event"];
@@ -19,11 +19,11 @@ export type AgentMetadataWatcher = {
1919
* Opens a websocket connection to watch metadata for a given workspace agent.
2020
* Emits onChange when metadata updates or an error occurs.
2121
*/
22-
export function createAgentMetadataWatcher(
22+
export async function createAgentMetadataWatcher(
2323
agentId: WorkspaceAgent["id"],
2424
client: CoderApi,
25-
): AgentMetadataWatcher {
26-
const socket = client.watchAgentMetadata(agentId);
25+
): Promise<AgentMetadataWatcher> {
26+
const socket = await client.watchAgentMetadata(agentId);
2727

2828
let disposed = false;
2929
const onChange = new vscode.EventEmitter<null>();

src/api/coderApi.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class CoderApi extends Api {
6767
return client;
6868
}
6969

70-
watchInboxNotifications = (
70+
watchInboxNotifications = async (
7171
watchTemplates: string[],
7272
watchTargets: string[],
7373
options?: ClientOptions,
@@ -83,14 +83,14 @@ export class CoderApi extends Api {
8383
});
8484
};
8585

86-
watchWorkspace = (workspace: Workspace, options?: ClientOptions) => {
86+
watchWorkspace = async (workspace: Workspace, options?: ClientOptions) => {
8787
return this.createWebSocket<ServerSentEvent>({
8888
apiRoute: `/api/v2/workspaces/${workspace.id}/watch-ws`,
8989
options,
9090
});
9191
};
9292

93-
watchAgentMetadata = (
93+
watchAgentMetadata = async (
9494
agentId: WorkspaceAgent["id"],
9595
options?: ClientOptions,
9696
) => {
@@ -100,21 +100,22 @@ export class CoderApi extends Api {
100100
});
101101
};
102102

103-
watchBuildLogsByBuildId = (buildId: string, logs: ProvisionerJobLog[]) => {
103+
watchBuildLogsByBuildId = async (
104+
buildId: string,
105+
logs: ProvisionerJobLog[],
106+
) => {
104107
const searchParams = new URLSearchParams({ follow: "true" });
105108
if (logs.length) {
106109
searchParams.append("after", logs[logs.length - 1].id.toString());
107110
}
108111

109-
const socket = this.createWebSocket<ProvisionerJobLog>({
112+
return this.createWebSocket<ProvisionerJobLog>({
110113
apiRoute: `/api/v2/workspacebuilds/${buildId}/logs`,
111114
searchParams,
112115
});
113-
114-
return socket;
115116
};
116117

117-
private createWebSocket<TData = unknown>(
118+
private async createWebSocket<TData = unknown>(
118119
configs: Omit<OneWayWebSocketInit, "location">,
119120
) {
120121
const baseUrlRaw = this.getAxiosInstance().defaults.baseURL;
@@ -127,14 +128,23 @@ export class CoderApi extends Api {
127128
coderSessionTokenHeader
128129
] as string | undefined;
129130

130-
const httpAgent = createHttpAgent(vscode.workspace.getConfiguration());
131+
const headers = await getHeaders(
132+
baseUrlRaw,
133+
getHeaderCommand(vscode.workspace.getConfiguration()),
134+
this.output,
135+
);
136+
137+
const httpAgent = await createHttpAgent(
138+
vscode.workspace.getConfiguration(),
139+
);
131140
const webSocket = new OneWayWebSocket<TData>({
132141
location: baseUrl,
133142
...configs,
134143
options: {
135144
agent: httpAgent,
136145
followRedirects: true,
137146
headers: {
147+
...headers,
138148
...(token ? { [coderSessionTokenHeader]: token } : {}),
139149
...configs.options?.headers,
140150
},
@@ -191,7 +201,7 @@ function setupInterceptors(
191201
// Configure proxy and TLS.
192202
// Note that by default VS Code overrides the agent. To prevent this, set
193203
// `http.proxySupport` to `on` or `off`.
194-
const agent = createHttpAgent(vscode.workspace.getConfiguration());
204+
const agent = await createHttpAgent(vscode.workspace.getConfiguration());
195205
config.httpsAgent = agent;
196206
config.httpAgent = agent;
197207
config.proxy = false;

src/api/utils.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import fs from "fs";
1+
import fs from "fs/promises";
22
import { ProxyAgent } from "proxy-agent";
33
import { type WorkspaceConfiguration } from "vscode";
44

@@ -23,7 +23,9 @@ export function needToken(cfg: WorkspaceConfiguration): boolean {
2323
* Create a new HTTP agent based on the current VS Code settings.
2424
* Configures proxy, TLS certificates, and security options.
2525
*/
26-
export function createHttpAgent(cfg: WorkspaceConfiguration): ProxyAgent {
26+
export async function createHttpAgent(
27+
cfg: WorkspaceConfiguration,
28+
): Promise<ProxyAgent> {
2729
const insecure = Boolean(cfg.get("coder.insecure"));
2830
const certFile = expandPath(
2931
String(cfg.get("coder.tlsCertFile") ?? "").trim(),
@@ -32,6 +34,12 @@ export function createHttpAgent(cfg: WorkspaceConfiguration): ProxyAgent {
3234
const caFile = expandPath(String(cfg.get("coder.tlsCaFile") ?? "").trim());
3335
const altHost = expandPath(String(cfg.get("coder.tlsAltHost") ?? "").trim());
3436

37+
const [cert, key, ca] = await Promise.all([
38+
certFile === "" ? Promise.resolve(undefined) : fs.readFile(certFile),
39+
keyFile === "" ? Promise.resolve(undefined) : fs.readFile(keyFile),
40+
caFile === "" ? Promise.resolve(undefined) : fs.readFile(caFile),
41+
]);
42+
3543
return new ProxyAgent({
3644
// Called each time a request is made.
3745
getProxyForUrl: (url: string) => {
@@ -41,9 +49,9 @@ export function createHttpAgent(cfg: WorkspaceConfiguration): ProxyAgent {
4149
cfg.get("coder.proxyBypass"),
4250
);
4351
},
44-
cert: certFile === "" ? undefined : fs.readFileSync(certFile),
45-
key: keyFile === "" ? undefined : fs.readFileSync(keyFile),
46-
ca: caFile === "" ? undefined : fs.readFileSync(caFile),
52+
cert,
53+
key,
54+
ca,
4755
servername: altHost === "" ? undefined : altHost,
4856
// rejectUnauthorized defaults to true, so we need to explicitly set it to
4957
// false if we want to allow self-signed certificates.

src/api/workspace.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ export async function waitForBuild(
9595
const logs = await client.getWorkspaceBuildLogs(workspace.latest_build.id);
9696
logs.forEach((log) => writeEmitter.fire(log.output + "\r\n"));
9797

98-
await new Promise<void>((resolve, reject) => {
99-
const socket = client.watchBuildLogsByBuildId(
100-
workspace.latest_build.id,
101-
logs,
102-
);
98+
const socket = await client.watchBuildLogsByBuildId(
99+
workspace.latest_build.id,
100+
logs,
101+
);
103102

103+
await new Promise<void>((resolve, reject) => {
104104
socket.addEventListener("message", (data) => {
105105
if (data.parseError) {
106106
writeEmitter.fire(

src/inbox.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,21 @@ const TEMPLATE_WORKSPACE_OUT_OF_MEMORY = "a9d027b4-ac49-4fb1-9f6d-45af15f64e7a";
1616
const TEMPLATE_WORKSPACE_OUT_OF_DISK = "f047f6a3-5713-40f7-85aa-0394cce9fa3a";
1717

1818
export class Inbox implements vscode.Disposable {
19-
readonly #logger: Logger;
20-
#disposed = false;
21-
#socket: OneWayWebSocket<GetInboxNotificationResponse>;
19+
private socket: OneWayWebSocket<GetInboxNotificationResponse> | undefined;
20+
private disposed = false;
2221

23-
constructor(workspace: Workspace, client: CoderApi, logger: Logger) {
24-
this.#logger = logger;
22+
private constructor(private readonly logger: Logger) {}
23+
24+
/**
25+
* Factory method to create and initialize an Inbox.
26+
* Use this instead of the constructor to properly handle async websocket initialization.
27+
*/
28+
static async create(
29+
workspace: Workspace,
30+
client: CoderApi,
31+
logger: Logger,
32+
): Promise<Inbox> {
33+
const inbox = new Inbox(logger);
2534

2635
const watchTemplates = [
2736
TEMPLATE_WORKSPACE_OUT_OF_DISK,
@@ -30,33 +39,40 @@ export class Inbox implements vscode.Disposable {
3039

3140
const watchTargets = [workspace.id];
3241

33-
this.#socket = client.watchInboxNotifications(watchTemplates, watchTargets);
42+
const socket = await client.watchInboxNotifications(
43+
watchTemplates,
44+
watchTargets,
45+
);
3446

35-
this.#socket.addEventListener("open", () => {
36-
this.#logger.info("Listening to Coder Inbox");
47+
socket.addEventListener("open", () => {
48+
logger.info("Listening to Coder Inbox");
3749
});
3850

39-
this.#socket.addEventListener("error", () => {
51+
socket.addEventListener("error", () => {
4052
// Errors are already logged internally
41-
this.dispose();
53+
inbox.dispose();
4254
});
4355

44-
this.#socket.addEventListener("message", (data) => {
56+
socket.addEventListener("message", (data) => {
4557
if (data.parseError) {
46-
this.#logger.error("Failed to parse inbox message", data.parseError);
58+
logger.error("Failed to parse inbox message", data.parseError);
4759
} else {
4860
vscode.window.showInformationMessage(
4961
data.parsedMessage.notification.title,
5062
);
5163
}
5264
});
65+
66+
inbox.socket = socket;
67+
68+
return inbox;
5369
}
5470

5571
dispose() {
56-
if (!this.#disposed) {
57-
this.#logger.info("No longer listening to Coder Inbox");
58-
this.#socket.close();
59-
this.#disposed = true;
72+
if (!this.disposed) {
73+
this.logger.info("No longer listening to Coder Inbox");
74+
this.socket?.close();
75+
this.disposed = true;
6076
}
6177
}
6278
}

src/remote/remote.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
getEventValue,
1919
formatEventLabel,
2020
formatMetadataError,
21-
} from "../agentMetadataHelper";
21+
} from "../api/agentMetadataHelper";
2222
import { createWorkspaceIdentifier, extractAgents } from "../api/api-helper";
2323
import { CoderApi } from "../api/coderApi";
2424
import { needToken } from "../api/utils";
@@ -543,7 +543,7 @@ export class Remote {
543543
}
544544

545545
// Watch the workspace for changes.
546-
const monitor = new WorkspaceMonitor(
546+
const monitor = await WorkspaceMonitor.create(
547547
workspace,
548548
workspaceClient,
549549
this.logger,
@@ -556,7 +556,7 @@ export class Remote {
556556
);
557557

558558
// Watch coder inbox for messages
559-
const inbox = new Inbox(workspace, workspaceClient, this.logger);
559+
const inbox = await Inbox.create(workspace, workspaceClient, this.logger);
560560
disposables.push(inbox);
561561

562562
// Wait for the agent to connect.
@@ -668,7 +668,7 @@ export class Remote {
668668
agent.name,
669669
);
670670
}),
671-
...this.createAgentMetadataStatusBar(agent, workspaceClient),
671+
...(await this.createAgentMetadataStatusBar(agent, workspaceClient)),
672672
);
673673
} catch (ex) {
674674
// Whatever error happens, make sure we clean up the disposables in case of failure
@@ -858,8 +858,7 @@ export class Remote {
858858
"UserKnownHostsFile",
859859
"StrictHostKeyChecking",
860860
];
861-
for (let i = 0; i < keysToMatch.length; i++) {
862-
const key = keysToMatch[i];
861+
for (const key of keysToMatch) {
863862
if (computedProperties[key] === sshValues[key]) {
864863
continue;
865864
}
@@ -1005,7 +1004,7 @@ export class Remote {
10051004
// this to find the SSH process that is powering this connection. That SSH
10061005
// process will be logging network information periodically to a file.
10071006
const text = await fs.readFile(logPath, "utf8");
1008-
const port = await findPort(text);
1007+
const port = findPort(text);
10091008
if (!port) {
10101009
return;
10111010
}
@@ -1064,16 +1063,16 @@ export class Remote {
10641063
* The status bar item updates dynamically based on changes to the agent's metadata,
10651064
* and hides itself if no metadata is available or an error occurs.
10661065
*/
1067-
private createAgentMetadataStatusBar(
1066+
private async createAgentMetadataStatusBar(
10681067
agent: WorkspaceAgent,
10691068
client: CoderApi,
1070-
): vscode.Disposable[] {
1069+
): Promise<vscode.Disposable[]> {
10711070
const statusBarItem = vscode.window.createStatusBarItem(
10721071
"agentMetadata",
10731072
vscode.StatusBarAlignment.Left,
10741073
);
10751074

1076-
const agentWatcher = createAgentMetadataWatcher(agent.id, client);
1075+
const agentWatcher = await createAgentMetadataWatcher(agent.id, client);
10771076

10781077
const onChangeDisposable = agentWatcher.onChange(() => {
10791078
if (agentWatcher.error) {

0 commit comments

Comments
 (0)