Skip to content

Commit c186fbe

Browse files
authored
feat(vscode): defined DartFrogApplicationRegistry (#933)
1 parent 87f0597 commit c186fbe

File tree

4 files changed

+1017
-0
lines changed

4 files changed

+1017
-0
lines changed
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
import {
2+
DaemonEvent,
3+
DaemonRequest,
4+
DartFrogApplication,
5+
DartFrogDaemon as DartFrogDaemon,
6+
DartFrogDaemonEventEmitterTypes,
7+
isApplicationExitDaemonEvent,
8+
isApplicationStartingDaemonEvent,
9+
isLoggerInfoDaemonEvent,
10+
isProgressCompleteDaemonEvent,
11+
isStartDaemonRequest,
12+
} from ".";
13+
import { EventEmitter } from "events";
14+
15+
/**
16+
* The prefix of the message that is sent by the Dart Frog Daemon when the Dart
17+
* VM service is listening.
18+
*
19+
* @example
20+
* "The Dart VM service is listening on http://127.0.0.1:8181/fQBcSu3OOc8=/"
21+
*/
22+
const vmServiceUriMessagePrefix = "The Dart VM service is listening on ";
23+
24+
/**
25+
* The prefix of the message that is sent by the Dart Frog Daemon when the
26+
* application is starting.
27+
*
28+
* @example
29+
* "Running on \u001b]8;;http://localhost:8080\u001b\\http://localhost:8080\u001b]8;;\u001b\\"
30+
*/
31+
const applicationStartingMessagePrefix = "Running on ";
32+
33+
/**
34+
* The regular expression that is used to extract an address from a message.
35+
*
36+
* @example
37+
* "Running on \u001b]8;;http://localhost:8080\u001b\\http://localhost:8080\u001b]8;;\u001b\\" -> "http://localhost:8080"
38+
*/
39+
const addressRegex = /http(s?):\/\/[^\u001b\\]+/;
40+
41+
/**
42+
* The types of events that are emitted by the
43+
* {@link DartFrogApplicationRegistry}.
44+
*
45+
* The possible types of events are:
46+
* - "add": When a new {@link DartFrogApplication} is added to the registry.
47+
* The {@link DartFrogApplication} is passed as an argument to the event
48+
* handler.
49+
* - "remove": When a {@link DartFrogApplication} is removed from the registry.
50+
* The {@link DartFrogApplication} is passed as an argument to the event
51+
* handler.
52+
*/
53+
export enum DartFrogApplicationRegistryEventEmitterTypes {
54+
add = "add",
55+
remove = "remove",
56+
}
57+
58+
/**
59+
* The Dart Frog applications that are currently running and managed by a Dart
60+
* Frog daemon.
61+
*/
62+
export class DartFrogApplicationRegistry {
63+
constructor(dartFrogDaemon: DartFrogDaemon) {
64+
this.dartFrogDaemon = dartFrogDaemon;
65+
66+
this.dartFrogDaemon.on(
67+
DartFrogDaemonEventEmitterTypes.request,
68+
this.startRequestListener.bind(this)
69+
);
70+
this.dartFrogDaemon.on(
71+
DartFrogDaemonEventEmitterTypes.event,
72+
this.applicationExitEventListener.bind(this)
73+
);
74+
}
75+
76+
private readonly dartFrogDaemon: DartFrogDaemon;
77+
78+
private readonly runningApplications: Map<String, DartFrogApplication> =
79+
new Map<String, DartFrogApplication>();
80+
81+
private readonly runningApplicationsEventEmitter = new EventEmitter();
82+
83+
/**
84+
* Retrieves all the Dart Frog applications that are currently
85+
* registered with this Dart Frog daemon.
86+
*/
87+
public all(): DartFrogApplication[] {
88+
const interator = this.runningApplications.values();
89+
return Array.from(interator);
90+
}
91+
92+
/**
93+
* Retrieves the Dart Frog application that is currently registered with this
94+
* Dart Frog daemon and has the given identifier.
95+
*
96+
* @param id The application identifier assigned by the Dart Frog daemon.
97+
* @returns The Dart Frog application that is currently registered with this
98+
* Dart Frog daemon and has the given identifier, or undefined if no such
99+
* application exists.
100+
*/
101+
public get(id: string): DartFrogApplication | undefined {
102+
return this.runningApplications.get(id);
103+
}
104+
105+
/**
106+
* Starts listening to events related to this application registry
107+
*
108+
* @returns A reference to this Dart Frog daemon, so that calls can be
109+
* chained.
110+
* @see {@link DartFrogDaemonEventEmitterTypes} for the types of events that
111+
* are emitted.
112+
*/
113+
public on(
114+
type: DartFrogApplicationRegistryEventEmitterTypes,
115+
listener: (...args: any[]) => void
116+
): DartFrogApplicationRegistry {
117+
this.runningApplicationsEventEmitter.on(type, listener);
118+
return this;
119+
}
120+
121+
/**
122+
* Unsubscribes a listener from events related to this application registry.
123+
*
124+
* @param type The type of event to unsubscribe from.
125+
* @param listener The listener to unsubscribe.
126+
* @returns A reference to this Dart Frog daemon application registry,
127+
* so that calls can be chained.
128+
*/
129+
public off(
130+
type: DartFrogApplicationRegistryEventEmitterTypes,
131+
listener: (...args: any[]) => void
132+
): DartFrogApplicationRegistry {
133+
this.runningApplicationsEventEmitter.off(type, listener);
134+
return this;
135+
}
136+
137+
private async startRequestListener(request: DaemonRequest): Promise<void> {
138+
if (!isStartDaemonRequest(request)) {
139+
return;
140+
}
141+
142+
const application = new DartFrogApplication(
143+
request.params.workingDirectory,
144+
request.params.port,
145+
request.params.dartVmServicePort
146+
);
147+
148+
const applicationId = this.retrieveApplicationId(request.id).then(
149+
(applicationId) => {
150+
application.id = applicationId;
151+
}
152+
);
153+
const vmServiceUri = this.retrieveVmServiceUri(request.id).then(
154+
(vmServiceUri) => {
155+
application.vmServiceUri = vmServiceUri;
156+
}
157+
);
158+
const address = this.retrieveAddress(request.id).then((address) => {
159+
application.address = address;
160+
});
161+
162+
await Promise.all([applicationId, vmServiceUri, address]);
163+
164+
if (!this.runningApplications.has(application.id!)) {
165+
this.register(application);
166+
}
167+
}
168+
169+
private async retrieveApplicationId(requestId: string): Promise<string> {
170+
return new Promise<string>((resolve) => {
171+
const applicationIdEventListener = (message: DaemonEvent) => {
172+
if (!isApplicationStartingDaemonEvent(message)) {
173+
return;
174+
} else if (message.params.requestId !== requestId) {
175+
return;
176+
}
177+
178+
this.dartFrogDaemon.off(
179+
DartFrogDaemonEventEmitterTypes.event,
180+
applicationIdEventListener
181+
);
182+
resolve(message.params.applicationId);
183+
};
184+
185+
this.dartFrogDaemon.on(
186+
DartFrogDaemonEventEmitterTypes.event,
187+
applicationIdEventListener.bind(this)
188+
);
189+
});
190+
}
191+
192+
private async retrieveVmServiceUri(requestId: string): Promise<string> {
193+
return new Promise<string>((resolve) => {
194+
const vmServiceUriEventListener = (event: DaemonEvent) => {
195+
if (
196+
!isLoggerInfoDaemonEvent(event) ||
197+
event.params.requestId !== requestId
198+
) {
199+
return;
200+
}
201+
202+
const message = event.params.message;
203+
if (!message.startsWith(vmServiceUriMessagePrefix)) {
204+
return;
205+
}
206+
207+
const vmServiceUri = message.match(addressRegex)![0];
208+
209+
this.dartFrogDaemon.off(
210+
DartFrogDaemonEventEmitterTypes.event,
211+
vmServiceUriEventListener
212+
);
213+
resolve(vmServiceUri);
214+
};
215+
216+
this.dartFrogDaemon.on(
217+
DartFrogDaemonEventEmitterTypes.event,
218+
vmServiceUriEventListener.bind(this)
219+
);
220+
});
221+
}
222+
223+
private async retrieveAddress(requestId: string): Promise<string> {
224+
return new Promise<string>((resolve) => {
225+
const addressEventListener = (message: DaemonEvent) => {
226+
if (
227+
!isProgressCompleteDaemonEvent(message) ||
228+
message.params.requestId !== requestId
229+
) {
230+
return;
231+
}
232+
233+
const progressMessage = message.params.progressMessage;
234+
if (!progressMessage.startsWith(applicationStartingMessagePrefix)) {
235+
return;
236+
}
237+
238+
const address = progressMessage.match(addressRegex)![0];
239+
this.dartFrogDaemon.off(
240+
DartFrogDaemonEventEmitterTypes.event,
241+
addressEventListener
242+
);
243+
resolve(address);
244+
};
245+
246+
this.dartFrogDaemon.on(
247+
DartFrogDaemonEventEmitterTypes.event,
248+
addressEventListener.bind(this)
249+
);
250+
});
251+
}
252+
253+
private applicationExitEventListener(event: DaemonEvent): void {
254+
if (!isApplicationExitDaemonEvent(event)) {
255+
return;
256+
}
257+
258+
const application = this.get(event.params.applicationId);
259+
if (application) {
260+
this.deregister(application);
261+
}
262+
}
263+
264+
private register(application: DartFrogApplication): void {
265+
this.runningApplications.set(application.id!, application);
266+
this.runningApplicationsEventEmitter.emit(
267+
DartFrogApplicationRegistryEventEmitterTypes.add,
268+
application
269+
);
270+
}
271+
272+
private deregister(application: DartFrogApplication): void {
273+
this.runningApplications.delete(application.id!);
274+
this.runningApplicationsEventEmitter.emit(
275+
DartFrogApplicationRegistryEventEmitterTypes.remove,
276+
application
277+
);
278+
}
279+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isReadyDaemonEvent,
1111
} from "./protocol";
1212
import { EventEmitter } from "events";
13+
import { DartFrogApplicationRegistry } from ".";
1314
import {
1415
AscendingNumericalIdentifierGenerator,
1516
IdentifierGenerator,
@@ -88,6 +89,13 @@ export class DartFrogDaemon {
8889
*/
8990
private process: ChildProcessWithoutNullStreams | undefined;
9091

92+
/**
93+
* A registry of the Dart Frog applications that are currently running on
94+
* this Dart Frog daemon.
95+
*/
96+
public readonly applicationRegistry: DartFrogApplicationRegistry =
97+
new DartFrogApplicationRegistry(this);
98+
9199
private _isReady: boolean = false;
92100

93101
/**

extensions/vscode/src/daemon/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./protocol";
22
export * from "./dart-frog-daemon";
33
export * from "./dart-frog-application";
4+
export * from "./dart-frog-application-registry";

0 commit comments

Comments
 (0)