11import { AxiosInstance , InternalAxiosRequestConfig , isAxiosError } from "axios" ;
22import { spawn } from "child_process" ;
33import { Api } from "coder/site/src/api/api" ;
4- import {
5- ProvisionerJobLog ,
6- Workspace ,
7- } from "coder/site/src/api/typesGenerated" ;
4+ import { Workspace } from "coder/site/src/api/typesGenerated" ;
85import fs from "fs/promises" ;
96import { ProxyAgent } from "proxy-agent" ;
107import * as vscode from "vscode" ;
11- import * as ws from "ws" ;
128import { errToStr } from "./api-helper" ;
139import { CertificateError } from "./error" ;
1410import { FeatureSet } from "./featureSet" ;
1511import { getHeaderArgs } from "./headers" ;
1612import { getProxyForUrl } from "./proxy" ;
1713import { Storage } from "./storage" ;
1814import { expandPath } from "./util" ;
15+ import { CoderWebSocketClient } from "./websocket/webSocketClient" ;
1916
2017export const coderSessionTokenHeader = "Coder-Session-Token" ;
2118
@@ -68,8 +65,12 @@ export async function createHttpAgent(): Promise<ProxyAgent> {
6865
6966/**
7067 * Create an sdk instance using the provided URL and token and hook it up to
71- * configuration. The token may be undefined if some other form of
68+ * configuration. The token may be undefined if some other form of
7269 * authentication is being used.
70+ *
71+ * Automatically configures logging interceptors that log:
72+ * - Requests and responses at the trace level
73+ * - Errors at the error level
7374 */
7475export function makeCoderSdk (
7576 baseUrl : string ,
@@ -128,14 +129,14 @@ function addLoggingInterceptors(
128129) {
129130 client . interceptors . request . use (
130131 ( config ) => {
131- const requestId = crypto . randomUUID ( ) ;
132+ const requestId = crypto . randomUUID ( ) . replace ( / - / g , "" ) ;
132133 ( config as RequestConfigWithMetadata ) . metadata = {
133134 requestId,
134135 startedAt : Date . now ( ) ,
135136 } ;
136137
137138 logger . trace (
138- `Request ${ requestId } : ${ config . method ?. toUpperCase ( ) } ${ config . url } ` ,
139+ `req ${ requestId } : ${ config . method ?. toUpperCase ( ) } ${ config . url } ` ,
139140 config . data ?? "" ,
140141 ) ;
141142
@@ -146,9 +147,9 @@ function addLoggingInterceptors(
146147 if ( isAxiosError ( error ) ) {
147148 const meta = ( error . config as RequestConfigWithMetadata ) ?. metadata ;
148149 const requestId = meta ?. requestId ?? "n/a" ;
149- message = `Request ${ requestId } error` ;
150+ message = `req ${ requestId } error` ;
150151 }
151- logger . warn ( message , error ) ;
152+ logger . error ( message , error ) ;
152153
153154 return Promise . reject ( error ) ;
154155 } ,
@@ -161,7 +162,7 @@ function addLoggingInterceptors(
161162 const ms = startedAt ? Date . now ( ) - startedAt : undefined ;
162163
163164 logger . trace (
164- `Response ${ requestId ?? "n/a" } : ${ response . status } ${
165+ `res ${ requestId ?? "n/a" } : ${ response . status } ${
165166 ms !== undefined ? ` in ${ ms } ms` : ""
166167 } `,
167168 // { responseBody: response.data }, // TODO too noisy
@@ -177,7 +178,7 @@ function addLoggingInterceptors(
177178 const ms = startedAt ? Date . now ( ) - startedAt : undefined ;
178179
179180 const status = error . response ?. status ?? "unknown" ;
180- message = `Response ${ requestId } : ${ status } ${ ms !== undefined ? ` in ${ ms } ms` : "" } ` ;
181+ message = `res ${ requestId } : ${ status } ${ ms !== undefined ? ` in ${ ms } ms` : "" } ` ;
181182 }
182183 logger . warn ( message , error ) ;
183184
@@ -264,70 +265,47 @@ export async function startWorkspaceIfStoppedOrFailed(
264265 */
265266export async function waitForBuild (
266267 restClient : Api ,
268+ webSocketClient : CoderWebSocketClient ,
267269 writeEmitter : vscode . EventEmitter < string > ,
268270 workspace : Workspace ,
269271) : Promise < Workspace > {
270- const baseUrlRaw = restClient . getAxiosInstance ( ) . defaults . baseURL ;
271- if ( ! baseUrlRaw ) {
272- throw new Error ( "No base URL set on REST client" ) ;
273- }
274-
275272 // This fetches the initial bunch of logs.
276273 const logs = await restClient . getWorkspaceBuildLogs (
277274 workspace . latest_build . id ,
278275 ) ;
279276 logs . forEach ( ( log ) => writeEmitter . fire ( log . output + "\r\n" ) ) ;
280277
281- // This follows the logs for new activity!
282- // TODO: watchBuildLogsByBuildId exists, but it uses `location`.
283- // Would be nice if we could use it here.
284- let path = `/api/v2/workspacebuilds/${ workspace . latest_build . id } /logs?follow=true` ;
285- if ( logs . length ) {
286- path += `&after=${ logs [ logs . length - 1 ] . id } ` ;
287- }
288-
289- const agent = await createHttpAgent ( ) ;
290278 await new Promise < void > ( ( resolve , reject ) => {
279+ const rejectError = ( error : unknown ) => {
280+ const baseUrlRaw = restClient . getAxiosInstance ( ) . defaults . baseURL ! ;
281+ return reject (
282+ new Error (
283+ `Failed to watch workspace build on ${ baseUrlRaw } : ${ errToStr ( error , "no further details" ) } ` ,
284+ ) ,
285+ ) ;
286+ } ;
287+
291288 try {
292- // TODO move to `ws-helper`
293- const baseUrl = new URL ( baseUrlRaw ) ;
294- const proto = baseUrl . protocol === "https:" ? "wss:" : "ws:" ;
295- const socketUrlRaw = `${ proto } //${ baseUrl . host } ${ path } ` ;
296- const token = restClient . getAxiosInstance ( ) . defaults . headers . common [
297- coderSessionTokenHeader
298- ] as string | undefined ;
299- const socket = new ws . WebSocket ( new URL ( socketUrlRaw ) , {
300- agent : agent ,
301- followRedirects : true ,
302- headers : token
303- ? {
304- [ coderSessionTokenHeader ] : token ,
305- }
306- : undefined ,
307- } ) ;
308- socket . binaryType = "nodebuffer" ;
309- socket . on ( "message" , ( data ) => {
310- const buf = data as Buffer ;
311- const log = JSON . parse ( buf . toString ( ) ) as ProvisionerJobLog ;
289+ const socket = webSocketClient . watchBuildLogsByBuildId (
290+ workspace . latest_build . id ,
291+ logs ,
292+ ) ;
293+ const closeHandler = ( ) => {
294+ resolve ( ) ;
295+ } ;
296+ socket . addEventListener ( "close" , closeHandler ) ;
297+ socket . addEventListener ( "message" , ( data ) => {
298+ const log = data . parsedMessage ! ;
312299 writeEmitter . fire ( log . output + "\r\n" ) ;
313300 } ) ;
314- socket . on ( "error" , ( error ) => {
315- reject (
316- new Error (
317- `Failed to watch workspace build using ${ socketUrlRaw } : ${ errToStr ( error , "no further details" ) } ` ,
318- ) ,
319- ) ;
320- } ) ;
321- socket . on ( "close" , ( ) => {
322- resolve ( ) ;
301+ socket . addEventListener ( "error" , ( error ) => {
302+ socket . removeEventListener ( "close" , closeHandler ) ;
303+ socket . close ( ) ;
304+ rejectError ( error ) ;
323305 } ) ;
324306 } catch ( error ) {
325307 // If this errors, it is probably a malformed URL.
326- reject (
327- new Error (
328- `Failed to watch workspace build on ${ baseUrlRaw } : ${ errToStr ( error , "no further details" ) } ` ,
329- ) ,
330- ) ;
308+ reject ( rejectError ) ;
331309 }
332310 } ) ;
333311
0 commit comments