Skip to content

Commit 87f0597

Browse files
authored
feat(vscode): allow daemon to send requests (#930)
1 parent a5432d4 commit 87f0597

File tree

3 files changed

+186
-1
lines changed

3 files changed

+186
-1
lines changed

extensions/vscode/src/daemon/dart-frog-daemon.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,26 @@ import {
1515
IdentifierGenerator,
1616
} from "../utils";
1717

18+
/**
19+
* An error that is thrown when the Dart Frog Daemon has not yet been invoked
20+
* but a request is made to it.
21+
*/
22+
export class DartFrogDaemonNotInvokedError extends Error {
23+
constructor() {
24+
super("The Dart Frog Daemon is yet to be invoked.");
25+
}
26+
}
27+
28+
/**
29+
* An error that is thrown when the Dart Frog Daemon is invoked but is not yet
30+
* ready to accept requests.
31+
*/
32+
export class DartFrogDaemonReadyError extends Error {
33+
constructor() {
34+
super("The Dart Frog Daemon is not yet ready to accept requests.");
35+
}
36+
}
37+
1838
/**
1939
* The types of events that are emitted by the {@link DartFrogDaemon}.
2040
*
@@ -102,8 +122,8 @@ export class DartFrogDaemon {
102122
const readyEventListener = (message: DaemonEvent) => {
103123
if (!this._isReady && isReadyDaemonEvent(message)) {
104124
this._isReady = true;
105-
resolve();
106125
this.off(DartFrogDaemonEventEmitterTypes.event, readyEventListener);
126+
resolve();
107127
}
108128
};
109129
this.on(
@@ -182,4 +202,47 @@ export class DartFrogDaemon {
182202
this.daemonMessagesEventEmitter.off(type, listener);
183203
return this;
184204
}
205+
206+
/**
207+
* Sends a request to the Dart Frog daemon.
208+
*
209+
* @param request The request to send to the Dart Frog daemon.
210+
* @throws {DartFrogDaemonNotInvokedError} If the Dart Frog daemon has not yet
211+
* been {@link invoke}d.
212+
* @throws {DartFrogDaemonReadyError} If the Dart Frog daemon is not yet
213+
* ready to accept requests.
214+
* @returns A promise that resolves to the response from the Dart Frog
215+
* daemon to the request.
216+
* @see {@link isReady} to check if the Dart Frog daemon is ready to accept
217+
* requests.
218+
*/
219+
public send(request: DaemonRequest): Promise<DaemonResponse> {
220+
if (!this.process) {
221+
throw new DartFrogDaemonNotInvokedError();
222+
} else if (!this.isReady) {
223+
throw new DartFrogDaemonReadyError();
224+
}
225+
226+
const responsePromise = new Promise<DaemonResponse>((resolve) => {
227+
const responseListener = (message: DaemonResponse) => {
228+
if (message.id === request.id && message.result) {
229+
this.off(DartFrogDaemonEventEmitterTypes.response, responseListener);
230+
resolve(message);
231+
}
232+
};
233+
this.on(
234+
DartFrogDaemonEventEmitterTypes.response,
235+
responseListener.bind(this)
236+
);
237+
});
238+
239+
const encodedRequest = `${JSON.stringify([request])}\n`;
240+
this.process!.stdin.write(encodedRequest);
241+
this.daemonMessagesEventEmitter.emit(
242+
DartFrogDaemonEventEmitterTypes.request,
243+
request
244+
);
245+
246+
return responsePromise;
247+
}
185248
}

extensions/vscode/src/daemon/protocol/domains/daemon.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
DaemonEvent,
1111
isDaemonEvent,
1212
isDaemonRequest,
13+
DaemonResponse,
1314
} from "../protocol";
1415

1516
const domainName = "daemon";

extensions/vscode/src/test/suite/daemon/dart-frog-daemon.test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EventEmitter } from "events";
44

55
import { afterEach, beforeEach } from "mocha";
66
import assert = require("assert");
7+
import { RequestVersionDaemonRequest } from "../../../daemon";
78

89
suite("DartFrogDaemon", () => {
910
let childProcessStub: any;
@@ -292,4 +293,124 @@ suite("DartFrogDaemon", () => {
292293
sinon.assert.notCalled(callback);
293294
});
294295
});
296+
297+
suite("send", () => {
298+
test("throws a DartFrogDaemonNotInvokedError when not invoked", async () => {
299+
const daemon = new dartFrogDaemon.DartFrogDaemon();
300+
301+
const request = new RequestVersionDaemonRequest("1");
302+
303+
assert.throws(
304+
() => daemon.send(request),
305+
new dartFrogDaemon.DartFrogDaemonNotInvokedError()
306+
);
307+
});
308+
309+
test("throws a DartFrogDaemonReadyError when invoked but not ready", async () => {
310+
const daemon = new dartFrogDaemon.DartFrogDaemon();
311+
312+
const daemonProcess = sinon.stub();
313+
const daemonStdoutEventEmitter = new EventEmitter();
314+
315+
daemonProcess.stdout = daemonStdoutEventEmitter;
316+
const workingDirectory = "workingDirectory";
317+
318+
childProcessStub.spawn
319+
.withArgs("dart_frog", ["daemon"], {
320+
cwd: workingDirectory,
321+
})
322+
.returns(daemonProcess);
323+
324+
daemon.invoke(workingDirectory);
325+
326+
const request = new RequestVersionDaemonRequest("1");
327+
328+
assert.throws(
329+
() => daemon.send(request),
330+
new dartFrogDaemon.DartFrogDaemonReadyError()
331+
);
332+
});
333+
334+
suite("when ready", () => {
335+
let daemon: any;
336+
let stdout: any;
337+
let stdin: any;
338+
339+
beforeEach(() => {
340+
const workingDirectory = "workingDirectory";
341+
342+
const daemonProcess = sinon.stub();
343+
344+
const daemonStdoutEventEmitter = new EventEmitter();
345+
daemonProcess.stdout = stdout = daemonStdoutEventEmitter;
346+
347+
daemonProcess.stdin = stdin = {
348+
write: sinon.stub(),
349+
};
350+
351+
childProcessStub.spawn
352+
.withArgs("dart_frog", ["daemon"], {
353+
cwd: workingDirectory,
354+
})
355+
.returns(daemonProcess);
356+
357+
daemon = new dartFrogDaemon.DartFrogDaemon();
358+
359+
const invokePromise = daemon.invoke(workingDirectory);
360+
361+
const readyMessage = `[{"event":"daemon.ready","params":{"version":"0.0.1","processId":94799}}]`;
362+
daemonStdoutEventEmitter.emit("data", readyMessage);
363+
364+
return invokePromise;
365+
});
366+
367+
test("writes request in stdin", async () => {
368+
const request = new RequestVersionDaemonRequest("1");
369+
370+
daemon.send(request);
371+
372+
sinon.assert.calledOnceWithExactly(
373+
stdin.write,
374+
`${JSON.stringify([request])}\n`
375+
);
376+
});
377+
378+
test("emits request event", async () => {
379+
const request = new RequestVersionDaemonRequest("1");
380+
381+
const callback = sinon.stub();
382+
daemon.on(
383+
dartFrogDaemon.DartFrogDaemonEventEmitterTypes.request,
384+
callback
385+
);
386+
387+
daemon.send(request);
388+
389+
sinon.assert.calledOnceWithExactly(callback, request);
390+
});
391+
392+
test("resolves correct response", async () => {
393+
const request = new RequestVersionDaemonRequest("1");
394+
395+
const responsePromise = daemon.send(request);
396+
397+
const anotherResponse = `[{"id":"2","result":{"version":"0.0.1"}}]`;
398+
stdout.emit("data", anotherResponse);
399+
400+
const response = `[{"id":"1","result":{"version":"0.0.1"}}]`;
401+
stdout.emit("data", response);
402+
403+
const actualResponse = await responsePromise;
404+
405+
const expectedResponse = {
406+
id: "1",
407+
result: {
408+
version: "0.0.1",
409+
},
410+
};
411+
412+
assert.deepEqual(actualResponse, expectedResponse);
413+
});
414+
});
415+
});
295416
});

0 commit comments

Comments
 (0)