microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7ef94dcec53e3855b735c2023711fb53cc5764af

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