microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7bc455e97274ff588d5fb1a0cfaccbc0d6fa56df

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/extensionServer.ts

261lines · 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 Q from "q";
5import * as vscode from "vscode";
6
7import {MessagingHelper}from "../common/extensionMessaging";
8import {OutputChannelLogger} from "./log/OutputChannelLogger";
9import {Packager} from "../common/packager";
10import {LogCatMonitor} from "./android/logCatMonitor";
11import {FileSystem} from "../common/node/fileSystem";
12import {SettingsHelper} from "./settingsHelper";
13import {Telemetry} from "../common/telemetry";
14import {PlatformResolver} from "./platformResolver";
15import {TelemetryHelper} from "../common/telemetryHelper";
16import {TargetPlatformHelper} from "../common/targetPlatformHelper";
17import {MobilePlatformDeps} from "./generalMobilePlatform";
18import {IRemoteExtension} from "../common/remoteExtension";
19import * as rpc from "noice-json-rpc";
20import * as WebSocket from "ws";
21import WebSocketServer = WebSocket.Server;
22
23export class ExtensionServer implements vscode.Disposable {
24 public api: IRemoteExtension;
25 public isDisposed: boolean = false;
26 private serverInstance: WebSocketServer | null;
27 private reactNativePackager: Packager;
28 private pipePath: string;
29 private logCatMonitor: LogCatMonitor | null = null;
30 private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
31
32 public constructor(projectRootPath: string, reactNativePackager: Packager) {
33 this.pipePath = MessagingHelper.getPath(projectRootPath);
34 this.reactNativePackager = reactNativePackager;
35 }
36
37 /**
38 * Starts the server.
39 */
40 public setup(): Q.Promise<void> {
41 this.isDisposed = false;
42 let deferred = Q.defer<void>();
43
44 let launchCallback = (error: any) => {
45 this.logger.debug(`Extension messaging server started at ${this.pipePath}.`);
46 deferred.resolve(void 0);
47 };
48
49 this.serverInstance = new WebSocketServer({port: <any>this.pipePath});
50 this.api = new rpc.Server(this.serverInstance).api();
51 this.serverInstance.on("listening", launchCallback.bind(this));
52 this.serverInstance.on("error", this.recoverServer.bind(this));
53
54 this.setupApiHandlers();
55
56 return deferred.promise;
57 }
58
59 /**
60 * Stops the server.
61 */
62 public dispose(): void {
63 this.isDisposed = true;
64 if (this.serverInstance) {
65 this.serverInstance.close();
66 this.serverInstance = null;
67 }
68
69 this.reactNativePackager.statusIndicator.dispose();
70 this.reactNativePackager.stop(true);
71 this.stopMonitoringLogCat();
72 }
73
74 private setupApiHandlers(): void {
75 let methods: any = {};
76 methods.stopMonitoringLogCat = this.stopMonitoringLogCat.bind(this);
77 methods.getPackagerPort = this.getPackagerPort.bind(this);
78 methods.sendTelemetry = this.sendTelemetry.bind(this);
79 methods.openFileAtLocation = this.openFileAtLocation.bind(this);
80 methods.showInformationMessage = this.showInformationMessage.bind(this);
81 methods.launch = this.launch.bind(this);
82 methods.showDevMenu = this.showDevMenu.bind(this);
83 methods.reloadApp = this.reloadApp.bind(this);
84
85 this.api.Extension.expose(methods);
86 }
87
88 private showDevMenu(deviceId?: string) {
89 this.api.Debugger.emitShowDevMenu(deviceId);
90 }
91
92 private reloadApp(deviceId?: string) {
93 this.api.Debugger.emitReloadApp(deviceId);
94 }
95
96 /**
97 * Recovers the server in case the named socket we use already exists, but no other instance of VSCode is active.
98 */
99 private recoverServer(error: any): void {
100 let errorHandler = (e: any) => {
101 /* The named socket is not used. */
102 if (e.code === "ECONNREFUSED") {
103 new FileSystem().removePathRecursivelyAsync(this.pipePath)
104 .then(() => {
105 return this.setup();
106 })
107 .done();
108 }
109 };
110
111 /* The named socket already exists. */
112 if (error.code === "EADDRINUSE") {
113 let clientSocket = new WebSocket(`ws+unix://${this.pipePath}`);
114 clientSocket.on("error", errorHandler);
115 clientSocket.on("open", function() {
116 clientSocket.close();
117 });
118 }
119 }
120
121 /**
122 * Message handler for GET_PACKAGER_PORT.
123 */
124 private getPackagerPort(program: string): number {
125 return SettingsHelper.getPackagerPort(program);
126 }
127
128 /**
129 * Message handler for OPEN_FILE_AT_LOCATION
130 */
131 private openFileAtLocation(filename: string, lineNumber: number): Promise<void> {
132 return new Promise((resolve) => {
133 vscode.workspace.openTextDocument(vscode.Uri.file(filename))
134 .then((document: vscode.TextDocument) => {
135 vscode.window.showTextDocument(document)
136 .then((editor: vscode.TextEditor) => {
137 let range = editor.document.lineAt(lineNumber - 1).range;
138 editor.selection = new vscode.Selection(range.start, range.end);
139 editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
140 resolve();
141 });
142 });
143 });
144 }
145
146 private stopMonitoringLogCat(): void {
147 if (this.logCatMonitor) {
148 this.logCatMonitor.dispose();
149 this.logCatMonitor = null;
150 }
151 }
152
153 /**
154 * Sends telemetry
155 */
156 private sendTelemetry(extensionId: string, extensionVersion: string, appInsightsKey: string, eventName: string, properties: {[key: string]: string}, measures: {[key: string]: number}): void {
157 Telemetry.sendExtensionTelemetry(extensionId, extensionVersion, appInsightsKey, eventName, properties, measures);
158 }
159
160 /**
161 * Message handler for SHOW_INFORMATION_MESSAGE
162 */
163 private showInformationMessage(message: string): void {
164 vscode.window.showInformationMessage(message);
165 }
166
167 private launch(request: any): Promise<any> {
168 let mobilePlatformOptions = requestSetup(request.arguments);
169
170 // We add the parameter if it's defined (adapter crashes otherwise)
171 if (!isNullOrUndefined(request.arguments.logCatArguments)) {
172 mobilePlatformOptions.logCatArguments = [parseLogCatArguments(request.arguments.logCatArguments)];
173 }
174
175 if (!isNullOrUndefined(request.arguments.variant)) {
176 mobilePlatformOptions.variant = request.arguments.variant;
177 }
178
179 if (!isNullOrUndefined(request.arguments.scheme)) {
180 mobilePlatformOptions.scheme = request.arguments.scheme;
181 }
182
183 mobilePlatformOptions.packagerPort = SettingsHelper.getPackagerPort(request.arguments.program);
184 const platformDeps: MobilePlatformDeps = {
185 packager: this.reactNativePackager,
186 };
187 const mobilePlatform = new PlatformResolver()
188 .resolveMobilePlatform(request.arguments.platform, mobilePlatformOptions, platformDeps);
189 return new Promise((resolve, reject) => {
190 TelemetryHelper.generate("launch", (generator) => {
191 generator.step("checkPlatformCompatibility");
192 TargetPlatformHelper.checkTargetPlatformSupport(mobilePlatformOptions.platform);
193 generator.step("startPackager");
194 return mobilePlatform.startPackager()
195 .then(() => {
196 // We've seen that if we don't prewarm the bundle cache, the app fails on the first attempt to connect to the debugger logic
197 // and the user needs to Reload JS manually. We prewarm it to prevent that issue
198 generator.step("prewarmBundleCache");
199 this.logger.info("Prewarming bundle cache. This may take a while ...");
200 return mobilePlatform.prewarmBundleCache();
201 })
202 .then(() => {
203 generator.step("mobilePlatform.runApp").add("target", mobilePlatformOptions.target, false);
204 this.logger.info("Building and running application.");
205 return mobilePlatform.runApp();
206 })
207 .then(() => {
208 generator.step("mobilePlatform.enableJSDebuggingMode");
209 this.logger.info("Enable JS Debugging");
210 return mobilePlatform.enableJSDebuggingMode();
211 })
212 .then(() => {
213 resolve();
214 })
215 .catch(error => {
216 this.logger.error(error);
217 reject(error);
218 });
219 });
220 });
221 }
222}
223
224/**
225 * Parses log cat arguments to a string
226 */
227function parseLogCatArguments(userProvidedLogCatArguments: any): string {
228 return Array.isArray(userProvidedLogCatArguments)
229 ? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
230 : userProvidedLogCatArguments; // If not, we leave it as-is
231}
232
233function isNullOrUndefined(value: any): boolean {
234 return typeof value === "undefined" || value === null;
235}
236
237function requestSetup(args: any): any {
238 const workspaceFolder: vscode.WorkspaceFolder = <vscode.WorkspaceFolder>vscode.workspace.getWorkspaceFolder(vscode.Uri.file(args.program));
239 const projectRootPath = getProjectRoot(args);
240 let mobilePlatformOptions: any = {
241 workspaceRoot: workspaceFolder.uri.fsPath,
242 projectRoot: projectRootPath,
243 platform: args.platform,
244 env: args.env,
245 envFile: args.envFile,
246 target: args.target || "simulator",
247 };
248
249 if (!args.runArguments) {
250 let runArgs = SettingsHelper.getRunArgs(args.platform, args.target || "simulator", workspaceFolder.uri);
251 mobilePlatformOptions.runArguments = runArgs;
252 } else {
253 mobilePlatformOptions.runArguments = args.runArguments;
254 }
255
256 return mobilePlatformOptions;
257}
258
259function getProjectRoot(args: any): string {
260 return SettingsHelper.getReactNativeProjectRoot(args.program);
261}
262