microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1ca47c7c2d73a604d8f110cdbad1aad4bbdddce6

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/extensionServer.ts

303lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as net from "net";
5import * as Q from "q";
6import * as vscode from "vscode";
7
8import * as em from "../common/extensionMessaging";
9import {Log} from "../common/log/log";
10import {LogLevel} from "../common/log/logHelper";
11import {Packager, PackagerRunAs} from "../common/packager";
12import {PackagerStatus, PackagerStatusIndicator} from "./packagerStatusIndicator";
13import {LogCatMonitor} from "./android/logCatMonitor";
14import {FileSystem} from "../common/node/fileSystem";
15import {ConfigurationReader} from "../common/configurationReader";
16import {SettingsHelper} from "./settingsHelper";
17import {Telemetry} from "../common/telemetry";
18import {ExponentHelper} from "../common/exponent/exponentHelper";
19
20export class ExtensionServer implements vscode.Disposable {
21 private serverInstance: net.Server | null = null;
22 private messageHandlerDictionary: { [id: number]: ((...argArray: any[]) => Q.Promise<any>) } = {};
23 private reactNativePackager: Packager;
24 private reactNativePackageStatusIndicator: PackagerStatusIndicator;
25 private pipePath: string;
26 private logCatMonitor: LogCatMonitor | null = null;
27 private exponentHelper: ExponentHelper;
28
29 public constructor(projectRootPath: string, reactNativePackager: Packager, packagerStatusIndicator: PackagerStatusIndicator, exponentHelper: ExponentHelper) {
30
31 this.pipePath = new em.MessagingChannel(projectRootPath).getPath();
32 this.reactNativePackager = reactNativePackager;
33 this.reactNativePackageStatusIndicator = packagerStatusIndicator;
34 this.exponentHelper = exponentHelper;
35
36 /* register handlers for all messages */
37 this.messageHandlerDictionary[em.ExtensionMessage.START_PACKAGER] = this.startPackager;
38 this.messageHandlerDictionary[em.ExtensionMessage.STOP_PACKAGER] = this.stopPackager;
39 this.messageHandlerDictionary[em.ExtensionMessage.RESTART_PACKAGER] = this.restartPackager;
40 this.messageHandlerDictionary[em.ExtensionMessage.PREWARM_BUNDLE_CACHE] = this.prewarmBundleCache;
41 this.messageHandlerDictionary[em.ExtensionMessage.START_MONITORING_LOGCAT] = this.startMonitoringLogCat;
42 this.messageHandlerDictionary[em.ExtensionMessage.STOP_MONITORING_LOGCAT] = this.stopMonitoringLogCat;
43 this.messageHandlerDictionary[em.ExtensionMessage.GET_PACKAGER_PORT] = this.getPackagerPort;
44 this.messageHandlerDictionary[em.ExtensionMessage.SEND_TELEMETRY] = this.sendTelemetry;
45 this.messageHandlerDictionary[em.ExtensionMessage.OPEN_FILE_AT_LOCATION] = this.openFileAtLocation;
46 this.messageHandlerDictionary[em.ExtensionMessage.START_EXPONENT_PACKAGER] = this.startExponentPackager;
47 this.messageHandlerDictionary[em.ExtensionMessage.SHOW_INFORMATION_MESSAGE] = this.showInformationMessage;
48 this.messageHandlerDictionary[em.ExtensionMessage.GET_RUN_ARGS] = this.getRunArgs;
49 }
50
51 /**
52 * Starts the server.
53 */
54 public setup(): Q.Promise<void> {
55
56 let deferred = Q.defer<void>();
57
58 let launchCallback = (error: any) => {
59 Log.logInternalMessage(LogLevel.Info, `Extension messaging server started at ${this.pipePath}.`);
60 if (error) {
61 deferred.reject(error);
62 } else {
63 deferred.resolve(void 0);
64 }
65 };
66
67 this.serverInstance = net.createServer(this.handleSocket.bind(this));
68 this.serverInstance.on("error", this.recoverServer.bind(this));
69 this.serverInstance.listen(this.pipePath, launchCallback);
70
71 return deferred.promise;
72 }
73
74 /**
75 * Stops the server.
76 */
77 public dispose(): void {
78 if (this.serverInstance) {
79 this.serverInstance.close();
80 this.serverInstance = null;
81 }
82
83 this.stopMonitoringLogCat();
84 }
85
86 /**
87 * Message handler for GET_PACKAGER_PORT.
88 */
89 private getPackagerPort(): Q.Promise<number> {
90 return Q(SettingsHelper.getPackagerPort());
91 }
92
93 /**
94 * Message handler for START_PACKAGER.
95 */
96 private startPackager(): Q.Promise<any> {
97 return this.reactNativePackager.isRunning().then((running) => {
98 if (running) {
99 if (this.reactNativePackager.getRunningAs() !== PackagerRunAs.REACT_NATIVE) {
100 return this.reactNativePackager.stop().then(() =>
101 this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STOPPED)
102 );
103 }
104
105 Log.logMessage("Attaching to running React Native packager");
106 }
107 return void 0;
108 })
109 .then(() => {
110 return this.reactNativePackager.startAsReactNative();
111 })
112 .then(() =>
113 this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED));
114 }
115
116 /**
117 * Message handler for START_EXPONENT_PACKAGER.
118 */
119 private startExponentPackager(): Q.Promise<any> {
120 return this.reactNativePackager.isRunning().then((running) => {
121 if (running) {
122 if (this.reactNativePackager.getRunningAs() !== PackagerRunAs.EXPONENT) {
123 return this.reactNativePackager.stop().then(() =>
124 this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STOPPED));
125 }
126
127 Log.logMessage("Attaching to running Exponent packager");
128 }
129 return void 0;
130 }).then(() =>
131 this.exponentHelper.configureExponentEnvironment()
132 ).then(() =>
133 this.exponentHelper.loginToExponent(
134 (message, password) => {
135 return Q.Promise((resolve, reject) => {
136 vscode.window.showInputBox({ placeHolder: message, password: password })
137 .then(resolve, reject);
138 });
139 },
140 (message) => {
141 return Q.Promise((resolve, reject) => {
142 vscode.window.showInformationMessage(message)
143 .then(resolve, reject);
144 });
145 }
146 ))
147 .then(() => {
148 return this.reactNativePackager.startAsExponent();
149 })
150 .then(exponentUrl => {
151 vscode.commands.executeCommand("vscode.previewHtml", vscode.Uri.parse(exponentUrl), 1, "Expo QR code");
152 this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.EXPONENT_PACKAGER_STARTED);
153 return exponentUrl;
154 });
155 }
156
157 /**
158 * Message handler for STOP_PACKAGER.
159 */
160 private stopPackager(): Q.Promise<any> {
161 return this.reactNativePackager.stop()
162 .then(() => this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STOPPED));
163 }
164
165 /**
166 * Message handler for RESTART_PACKAGER.
167 */
168 private restartPackager(port?: any): Q.Promise<any> {
169 const portToUse = ConfigurationReader.readIntWithDefaultSync(port, SettingsHelper.getPackagerPort());
170 return this.reactNativePackager.restart(portToUse)
171 .then(() =>
172 this.reactNativePackageStatusIndicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED));
173 }
174
175 /**
176 * Message handler for PREWARM_BUNDLE_CACHE.
177 */
178 private prewarmBundleCache(platform: string): Q.Promise<any> {
179 return this.reactNativePackager.prewarmBundleCache(platform);
180 }
181
182 /**
183 * Message handler for START_MONITORING_LOGCAT.
184 */
185 private startMonitoringLogCat(deviceId: string, logCatArguments: string): Q.Promise<any> {
186 this.stopMonitoringLogCat(); // Stop previous logcat monitor if it's running
187
188 // this.logCatMonitor can be mutated, so we store it locally too
189 const logCatMonitor = this.logCatMonitor = new LogCatMonitor(deviceId, logCatArguments);
190 logCatMonitor.start() // The LogCat will continue running forever, so we don't wait for it
191 .catch(error =>
192 Log.logWarning("Error while monitoring LogCat", error))
193 .done();
194
195 return Q.resolve<void>(void 0);
196 }
197
198 /**
199 * Message handler for OPEN_FILE_AT_LOCATION
200 */
201 private openFileAtLocation(filename: string, lineNumber: number): Q.Promise<PromiseLike<void>> {
202 return Q(vscode.workspace.openTextDocument(vscode.Uri.file(filename)).then((document: vscode.TextDocument) => {
203 return vscode.window.showTextDocument(document).then((editor: vscode.TextEditor) => {
204 let range = editor.document.lineAt(lineNumber - 1).range;
205 editor.selection = new vscode.Selection(range.start, range.end);
206 editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
207 });
208 }));
209 }
210
211 private stopMonitoringLogCat(): Q.Promise<void> {
212 if (this.logCatMonitor) {
213 this.logCatMonitor.dispose();
214 this.logCatMonitor = null;
215 }
216
217 return Q.resolve<void>(void 0);
218 }
219
220 /**
221 * Sends telemetry
222 */
223 private sendTelemetry(extensionId: string, extensionVersion: string, appInsightsKey: string, eventName: string, properties: {[key: string]: string}, measures: {[key: string]: number}): Q.Promise<any> {
224 Telemetry.sendExtensionTelemetry(extensionId, extensionVersion, appInsightsKey, eventName, properties, measures);
225 return Q.resolve({});
226 }
227
228 /**
229 * Extension message handler.
230 */
231 private handleExtensionMessage(messageWithArgs: em.MessageWithArguments): Q.Promise<any> {
232 let handler = this.messageHandlerDictionary[messageWithArgs.message];
233 if (handler) {
234 Log.logInternalMessage(LogLevel.Info, "Handling message: " + em.ExtensionMessage[messageWithArgs.message]);
235 return handler.apply(this, messageWithArgs.args);
236 } else {
237 return Q.reject("Invalid message: " + messageWithArgs.message);
238 }
239 }
240
241 /**
242 * Handles connections to the server.
243 */
244 private handleSocket(socket: net.Socket): void {
245 let handleError = (e: any) => {
246 Log.logError(e);
247 socket.end(em.ErrorMarker);
248 };
249
250 let dataCallback = (data: any) => {
251 try {
252 let messageWithArgs: em.MessageWithArguments = JSON.parse(data);
253 this.handleExtensionMessage(messageWithArgs)
254 .then(result => {
255 socket.end(JSON.stringify(result));
256 })
257 .catch((e) => { handleError(e); })
258 .done();
259 } catch (e) {
260 handleError(e);
261 }
262 };
263
264 socket.on("data", dataCallback);
265 }
266
267 /**
268 * Recovers the server in case the named socket we use already exists, but no other instance of VSCode is active.
269 */
270 private recoverServer(error: any): void {
271 let errorHandler = (e: any) => {
272 /* The named socket is not used. */
273 if (e.code === "ECONNREFUSED") {
274 new FileSystem().removePathRecursivelyAsync(this.pipePath)
275 .then(() => {
276 if (this.serverInstance) {
277 this.serverInstance.listen(this.pipePath);
278 }
279 })
280 .done();
281 }
282 };
283
284 /* The named socket already exists. */
285 if (error.code === "EADDRINUSE") {
286 let clientSocket = new net.Socket();
287 clientSocket.on("error", errorHandler);
288 clientSocket.connect(this.pipePath, function() {
289 clientSocket.end();
290 });
291 }
292 }
293
294 /**
295 * Message handler for SHOW_INFORMATION_MESSAGE
296 */
297 private showInformationMessage(message: string): Q.Promise<void> {
298 return Q(vscode.window.showInformationMessage(message)).then(() => {});
299 }
300 private getRunArgs(platform: string, target: "device" | "simulator"): Q.Promise<string[]> {
301 return Q.resolve(SettingsHelper.getRunArgs(platform, target));
302 }
303}