microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.11.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/extensionServer.ts

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