microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.4.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/extensionServer.ts

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