Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita401805

Browse files
authored
Fixed WebSocket connections not receiving headers from the configured header command (#619)
Closes#618
1 parent2cd05a3 commita401805

File tree

10 files changed

+167
-107
lines changed

10 files changed

+167
-107
lines changed

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
##Unreleased
44

5+
###Fixed
6+
7+
- Fixed WebSocket connections not receiving headers from the configured header command
8+
(`coder.headerCommand`), which could cause authentication failures with remote workspaces.
9+
510
##[v1.11.2](https://github.com/coder/vscode-coder/releases/tag/v1.11.2) 2025-10-07
611

712
###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
typeAgentMetadataEvent,
66
AgentMetadataEventSchemaArray,
77
errToStr,
8-
}from"./api/api-helper";
9-
import{typeCoderApi}from"./api/coderApi";
8+
}from"./api-helper";
9+
import{typeCoderApi}from"./coderApi";
1010

1111
exporttypeAgentMetadataWatcher={
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-
exportfunctioncreateAgentMetadataWatcher(
22+
exportasyncfunctioncreateAgentMetadataWatcher(
2323
agentId:WorkspaceAgent["id"],
2424
client:CoderApi,
25-
):AgentMetadataWatcher{
26-
constsocket=client.watchAgentMetadata(agentId);
25+
):Promise<AgentMetadataWatcher>{
26+
constsocket=awaitclient.watchAgentMetadata(agentId);
2727

2828
letdisposed=false;
2929
constonChange=newvscode.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
returnclient;
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
returnthis.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
constsearchParams=newURLSearchParams({follow:"true"});
105108
if(logs.length){
106109
searchParams.append("after",logs[logs.length-1].id.toString());
107110
}
108111

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

117-
privatecreateWebSocket<TData=unknown>(
118+
privateasynccreateWebSocket<TData=unknown>(
118119
configs:Omit<OneWayWebSocketInit,"location">,
119120
){
120121
constbaseUrlRaw=this.getAxiosInstance().defaults.baseURL;
@@ -127,7 +128,15 @@ export class CoderApi extends Api {
127128
coderSessionTokenHeader
128129
]asstring|undefined;
129130

130-
consthttpAgent=createHttpAgent(vscode.workspace.getConfiguration());
131+
constheaders=awaitgetHeaders(
132+
baseUrlRaw,
133+
getHeaderCommand(vscode.workspace.getConfiguration()),
134+
this.output,
135+
);
136+
137+
consthttpAgent=awaitcreateHttpAgent(
138+
vscode.workspace.getConfiguration(),
139+
);
131140
constwebSocket=newOneWayWebSocket<TData>({
132141
location:baseUrl,
133142
...configs,
@@ -137,6 +146,7 @@ export class CoderApi extends Api {
137146
headers:{
138147
...(token ?{[coderSessionTokenHeader]:token} :{}),
139148
...configs.options?.headers,
149+
...headers,
140150
},
141151
...configs.options,
142152
},
@@ -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-
constagent=createHttpAgent(vscode.workspace.getConfiguration());
204+
constagent=awaitcreateHttpAgent(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-
importfsfrom"fs";
1+
importfsfrom"fs/promises";
22
import{ProxyAgent}from"proxy-agent";
33
import{typeWorkspaceConfiguration}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-
exportfunctioncreateHttpAgent(cfg:WorkspaceConfiguration):ProxyAgent{
26+
exportasyncfunctioncreateHttpAgent(
27+
cfg:WorkspaceConfiguration,
28+
):Promise<ProxyAgent>{
2729
constinsecure=Boolean(cfg.get("coder.insecure"));
2830
constcertFile=expandPath(
2931
String(cfg.get("coder.tlsCertFile")??"").trim(),
@@ -32,6 +34,12 @@ export function createHttpAgent(cfg: WorkspaceConfiguration): ProxyAgent {
3234
constcaFile=expandPath(String(cfg.get("coder.tlsCaFile")??"").trim());
3335
constaltHost=expandPath(String(cfg.get("coder.tlsAltHost")??"").trim());
3436

37+
const[cert,key,ca]=awaitPromise.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
returnnewProxyAgent({
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
constlogs=awaitclient.getWorkspaceBuildLogs(workspace.latest_build.id);
9696
logs.forEach((log)=>writeEmitter.fire(log.output+"\r\n"));
9797

98-
awaitnewPromise<void>((resolve,reject)=>{
99-
constsocket=client.watchBuildLogsByBuildId(
100-
workspace.latest_build.id,
101-
logs,
102-
);
98+
constsocket=awaitclient.watchBuildLogsByBuildId(
99+
workspace.latest_build.id,
100+
logs,
101+
);
103102

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

‎src/headers.ts‎

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function getHeaderCommand(
2424
config.get<string>("coder.headerCommand")?.trim()||
2525
process.env.CODER_HEADER_COMMAND?.trim();
2626

27-
returncmd?cmd :undefined;
27+
returncmd||undefined;
2828
}
2929

3030
exportfunctiongetHeaderArgs(config:WorkspaceConfiguration):string[]{
@@ -44,16 +44,13 @@ export function getHeaderArgs(config: WorkspaceConfiguration): string[] {
4444
return["--header-command",escapeSubcommand(command)];
4545
}
4646

47-
// TODO: getHeaders might make more sense to directly implement on Storage
48-
// but it is difficult to test Storage right now since we use vitest instead of
49-
// the standard extension testing framework which would give us access to vscode
50-
// APIs. We should revert the testing framework then consider moving this.
51-
52-
// getHeaders executes the header command and parses the headers from stdout.
53-
// Both stdout and stderr are logged on error but stderr is otherwise ignored.
54-
// Throws an error if the process exits with non-zero or the JSON is invalid.
55-
// Returns undefined if there is no header command set. No effort is made to
56-
// validate the JSON other than making sure it can be parsed.
47+
/**
48+
* getHeaders executes the header command and parses the headers from stdout.
49+
* Both stdout and stderr are logged on error but stderr is otherwise ignored.
50+
* Throws an error if the process exits with non-zero or the JSON is invalid.
51+
* Returns undefined if there is no header command set. No effort is made to
52+
* validate the JSON other than making sure it can be parsed.
53+
*/
5754
exportasyncfunctiongetHeaders(
5855
url:string|undefined,
5956
command:string|undefined,
@@ -90,8 +87,8 @@ export async function getHeaders(
9087
returnheaders;
9188
}
9289
constlines=result.stdout.replace(/\r?\n$/,"").split(/\r?\n/);
93-
for(leti=0;i<lines.length;++i){
94-
const[key,value]=lines[i].split(/=(.*)/);
90+
for(constlineoflines){
91+
const[key,value]=line.split(/=(.*)/);
9592
// Header names cannot be blank or contain whitespace and the Coder CLI
9693
// requires that there be an equals sign (the value can be blank though).
9794
if(
@@ -100,7 +97,7 @@ export async function getHeaders(
10097
typeofvalue==="undefined"
10198
){
10299
thrownewError(
103-
`Malformed line from header command: [${lines[i]}] (out:${result.stdout})`,
100+
`Malformed line from header command: [${line}] (out:${result.stdout})`,
104101
);
105102
}
106103
headers[key]=value;

‎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
constTEMPLATE_WORKSPACE_OUT_OF_DISK="f047f6a3-5713-40f7-85aa-0394cce9fa3a";
1717

1818
exportclassInboximplementsvscode.Disposable{
19-
readonly #logger:Logger;
20-
#disposed=false;
21-
#socket:OneWayWebSocket<GetInboxNotificationResponse>;
19+
privatesocket:OneWayWebSocket<GetInboxNotificationResponse>|undefined;
20+
privatedisposed=false;
2221

23-
constructor(workspace:Workspace,client:CoderApi,logger:Logger){
24-
this.#logger=logger;
22+
privateconstructor(privatereadonlylogger: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+
staticasynccreate(
29+
workspace:Workspace,
30+
client:CoderApi,
31+
logger:Logger,
32+
):Promise<Inbox>{
33+
constinbox=newInbox(logger);
2534

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

3140
constwatchTargets=[workspace.id];
3241

33-
this.#socket=client.watchInboxNotifications(watchTemplates,watchTargets);
42+
constsocket=awaitclient.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+
returninbox;
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
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp