diff --git a/package.json b/package.json index 3ae8258c..b251492d 100644 --- a/package.json +++ b/package.json @@ -429,7 +429,6 @@ "@semantic-release/changelog": "^5.0.1", "@semantic-release/exec": "^5.0.0", "@semantic-release/git": "^9.0.0", - "@types/bent": "^7.3.2", "@types/chai-as-promised": "^7.1.5", "@types/chai-fs": "^2.0.2", "@types/fs-extra": "^9.0.13", @@ -496,7 +495,6 @@ "@appland/scanner": "^1.83.0", "@appland/sequence-diagram": "^1.11.0", "@yarnpkg/parsers": "^3.0.0-rc.45", - "bent": "^7.3.12", "bootstrap": "^4.5.3", "bootstrap-autocomplete": "^2.3.7", "diff": "^5.1.0", diff --git a/src/actions/remoteRecording.ts b/src/actions/remoteRecording.ts index 16f0d073..4f5485a3 100644 --- a/src/actions/remoteRecording.ts +++ b/src/actions/remoteRecording.ts @@ -109,7 +109,10 @@ export default class RemoteRecording { ); this.onBeginRecording(recordingUrl); } catch (e) { - vscode.window.showErrorMessage(`The endpoint does not support AppMap recording`); + const err = e as Error; + vscode.window.showErrorMessage( + `Failed to start recording on ${recordingUrl}: ${err.message}` + ); return; } diff --git a/src/actions/remoteRecordingClient.ts b/src/actions/remoteRecordingClient.ts index 7613d870..64d5f7d8 100644 --- a/src/actions/remoteRecordingClient.ts +++ b/src/actions/remoteRecordingClient.ts @@ -1,35 +1,50 @@ -import bent, { NodeResponse } from 'bent'; +import http, { type IncomingMessage } from 'node:http'; +import https from 'node:https'; export default class RemoteRecordingClient { - private static readonly RECORDING_URI = '/_appmap/record'; - static async start(baseURL: string): Promise { - const getStream = bent(baseURL, 'POST', 200, 409); - const stream = (await getStream(this.RECORDING_URI)) as { - statusCode: number; - }; - - return stream.statusCode; + return (await recordRequest(baseURL, 'POST', [200, 409])).statusCode; } static async getStatus(baseURL: string): Promise { - const request = bent(baseURL, 'GET', 200); - const response = (await request(this.RECORDING_URI)) as NodeResponse; - const body = (await response.json()) as { enabled: boolean }; - - return body.enabled; + const response = await recordRequest(baseURL, 'GET', [200]); + const body = await readJSON(response); + if (typeof body === 'object' && body && 'enabled' in body && typeof body.enabled === 'boolean') + return body.enabled; + else throw new Error(`Unexpected body: ${body}`); } - static async stop(baseURL: string): Promise { - const getStream = bent(baseURL, 'DELETE', 200, 404); - const stream = (await getStream(this.RECORDING_URI)) as { - statusCode: number; - json; - }; - + static async stop(baseURL: string): Promise<{ statusCode: number; body: unknown }> { + const response = await recordRequest(baseURL, 'DELETE', [200, 404]); return { - statusCode: stream.statusCode, - body: await stream.json(), + statusCode: response.statusCode, + body: await readJSON(response).catch(() => undefined), }; } } + +function recordRequest( + baseURL: string, + method: string, + codes: number[] +): Promise { + if (!baseURL.startsWith('http')) baseURL = `http://${baseURL}`; + const url = new URL('_appmap/record', baseURL); + const proto = url.protocol === 'https:' ? https : http; + return new Promise((resolve, reject) => + proto + .request(url, { method }, (response) => + response.statusCode && codes.includes(response.statusCode) + ? resolve(response as IncomingMessage & { statusCode: number }) + : reject(new Error(`unexpected response code: ${response.statusCode}`)) + ) + .once('error', reject) + .end() + ); +} + +async function readJSON(res: NodeJS.ReadableStream): Promise { + const chunks: string[] = []; + for await (const chunk of res) chunks.push(chunk.toString()); + return JSON.parse(chunks.join('')); +} diff --git a/test/unit/actions/remoteRecording.test.ts b/test/unit/actions/remoteRecording.test.ts index 6dc62086..81c1afa0 100644 --- a/test/unit/actions/remoteRecording.test.ts +++ b/test/unit/actions/remoteRecording.test.ts @@ -50,7 +50,7 @@ describe('remote recording', () => { }); it('returns false on command stop with a 404 response', async () => { - sinon.stub(RemoteRecordingClient, 'stop').resolves({ statusCode: 404 }); + sinon.stub(RemoteRecordingClient, 'stop').resolves({ statusCode: 404, body: undefined }); const response = await remoteRecording.stop('http://localhost:8080/'); assert(!response); }); diff --git a/yarn.lock b/yarn.lock index 26c7e84a..c0582fe5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1843,15 +1843,6 @@ __metadata: languageName: node linkType: hard -"@types/bent@npm:^7.3.2": - version: 7.3.2 - resolution: "@types/bent@npm:7.3.2" - dependencies: - "@types/node": "*" - checksum: af28b4e77fce3de928ac4b90568971b73970351d3167bf89418c363ab207164516745b477ed803c90b2aaf81d46487e298e4fa8febd45b2d109e80872670c75f - languageName: node - linkType: hard - "@types/btoa-lite@npm:^1.0.0": version: 1.0.0 resolution: "@types/btoa-lite@npm:1.0.0" @@ -2816,7 +2807,6 @@ __metadata: "@semantic-release/changelog": ^5.0.1 "@semantic-release/exec": ^5.0.0 "@semantic-release/git": ^9.0.0 - "@types/bent": ^7.3.2 "@types/chai-as-promised": ^7.1.5 "@types/chai-fs": ^2.0.2 "@types/fs-extra": ^9.0.13 @@ -2838,7 +2828,6 @@ __metadata: "@vscode/vsce": ^2.19.0 "@vue/compiler-sfc": ^3.2.37 "@yarnpkg/parsers": ^3.0.0-rc.45 - bent: ^7.3.12 bootstrap: ^4.5.3 bootstrap-autocomplete: ^2.3.7 browserify-fs: ^1.0.0 @@ -3208,17 +3197,6 @@ __metadata: languageName: node linkType: hard -"bent@npm:^7.3.12": - version: 7.3.12 - resolution: "bent@npm:7.3.12" - dependencies: - bytesish: ^0.4.1 - caseless: ~0.12.0 - is-stream: ^2.0.0 - checksum: b0c08f6fa204baec0841021f5cff49aba929bbff28089bbac0d446122aa5f8e10b1b6837310b0800717cbf431c984bead2ed130bec35045d4ea688d896458d54 - languageName: node - linkType: hard - "big-integer@npm:^1.6.17": version: 1.6.48 resolution: "big-integer@npm:1.6.48" @@ -3599,13 +3577,6 @@ __metadata: languageName: node linkType: hard -"bytesish@npm:^0.4.1": - version: 0.4.4 - resolution: "bytesish@npm:0.4.4" - checksum: 50a6c9423f66fff984676ee1d3c5f12d2a23830cb5de81abc597415aa2f51cc617b251d660eede10b8e45bf3a3a5c31d4be467ec69888f31ccc66e90cb586ad5 - languageName: node - linkType: hard - "cac@npm:^6.7.12": version: 6.7.14 resolution: "cac@npm:6.7.14"