microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4b124a08cf4726fc4b6b1f843dcd24e31f33db5e

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

248lines · 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 path from "path";
6import * as http from "http";
7
8import {Telemetry} from "../common/telemetry";
9import {TelemetryHelper} from "../common/telemetryHelper";
10import {RemoteExtension} from "../common/remoteExtension";
11import {IOSPlatform} from "./ios/iOSPlatform";
12import {PlatformResolver} from "./platformResolver";
13import {IRunOptions} from "../common/launchArgs";
14import {TargetPlatformHelper} from "../common/targetPlatformHelper";
15import {ExtensionTelemetryReporter, ReassignableTelemetryReporter} from "../common/telemetryReporters";
16import {NodeDebugAdapterLogger} from "../common/log/loggers";
17import {Log} from "../common/log/log";
18import {GeneralMobilePlatform} from "./generalMobilePlatform";
19
20export class NodeDebugWrapper {
21 private projectRootPath: string;
22 private remoteExtension: RemoteExtension;
23 private telemetryReporter: ReassignableTelemetryReporter;
24 private appName: string;
25 private version: string;
26 private mobilePlatformOptions: IRunOptions;
27
28 private vscodeDebugAdapterPackage: typeof VSCodeDebugAdapter;
29 private nodeDebugSession: typeof NodeDebugSession;
30 private originalLaunchRequest: (response: any, args: any) => void;
31
32 public constructor(appName: string, version: string, telemetryReporter: ReassignableTelemetryReporter, debugAdapter: typeof VSCodeDebugAdapter, debugSession: typeof NodeDebugSession) {
33 this.appName = appName;
34 this.version = version;
35 this.telemetryReporter = telemetryReporter;
36 this.vscodeDebugAdapterPackage = debugAdapter;
37 this.nodeDebugSession = debugSession;
38 this.originalLaunchRequest = this.nodeDebugSession.prototype.launchRequest;
39 }
40
41 /**
42 * Calls customize methods for all requests needed
43 */
44 public customizeNodeAdapterRequests(): void {
45 this.customizeLaunchRequest();
46 this.customizeAttachRequest();
47 this.customizeDisconnectRequest();
48 }
49
50 /**
51 * Intecept the "launchRequest" instance method of NodeDebugSession to interpret arguments.
52 * Launch should:
53 * - Run the packager if needed
54 * - Compile and run application
55 * - Prewarm bundle
56 */
57 private customizeLaunchRequest(): void {
58 const nodeDebugWrapper = this;
59 this.nodeDebugSession.prototype.launchRequest = function (request: any, args: ILaunchRequestArgs) {
60 nodeDebugWrapper.requestSetup(this, args);
61 nodeDebugWrapper.mobilePlatformOptions.target = args.target || "simulator";
62 nodeDebugWrapper.mobilePlatformOptions.iosRelativeProjectPath = !nodeDebugWrapper.isNullOrUndefined(args.iosRelativeProjectPath) ?
63 args.iosRelativeProjectPath :
64 IOSPlatform.DEFAULT_IOS_PROJECT_RELATIVE_PATH;
65
66 // We add the parameter if it's defined (adapter crashes otherwise)
67 if (!nodeDebugWrapper.isNullOrUndefined(args.logCatArguments)) {
68 nodeDebugWrapper.mobilePlatformOptions.logCatArguments = [nodeDebugWrapper.parseLogCatArguments(args.logCatArguments)];
69 }
70
71 return TelemetryHelper.generate("launch", (generator) => {
72 const resolver = new PlatformResolver();
73 return nodeDebugWrapper.remoteExtension.getPackagerPort()
74 .then(packagerPort => {
75 nodeDebugWrapper.mobilePlatformOptions.packagerPort = packagerPort;
76 const mobilePlatform = resolver.resolveMobilePlatform(args.platform, nodeDebugWrapper.mobilePlatformOptions);
77 return Q({})
78 .then(() => {
79 generator.step("checkPlatformCompatibility");
80 TargetPlatformHelper.checkTargetPlatformSupport(nodeDebugWrapper.mobilePlatformOptions.platform);
81 generator.step("startPackager");
82 Log.logMessage("Starting React Native Packager.");
83 return mobilePlatform.startPackager();
84 })
85 .then(() => {
86 // 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
87 // and the user needs to Reload JS manually. We prewarm it to prevent that issue
88 generator.step("prewarmBundleCache");
89 Log.logMessage("Prewarming bundle cache. This may take a while ...");
90 return mobilePlatform.prewarmBundleCache();
91 })
92 .then(() => {
93 generator.step("mobilePlatform.runApp");
94 Log.logMessage("Building and running application.");
95 return mobilePlatform.runApp();
96 })
97 .then(() =>
98 nodeDebugWrapper.attachRequest(this, request, args, mobilePlatform));
99 }).catch(error =>
100 nodeDebugWrapper.bailOut(this, error.message));
101 });
102 };
103 }
104
105 /**
106 * Intecept the "attachRequest" instance method of NodeDebugSession to interpret arguments
107 */
108 private customizeAttachRequest(): void {
109 const nodeDebugWrapper = this;
110 this.nodeDebugSession.prototype.attachRequest = function (request: any, args: IAttachRequestArgs) {
111 nodeDebugWrapper.requestSetup(this, args);
112 nodeDebugWrapper.attachRequest(this, request, args, new GeneralMobilePlatform(nodeDebugWrapper.mobilePlatformOptions));
113 };
114 }
115
116 /**
117 * Intecept the "disconnectRequest" instance method of NodeDebugSession to interpret arguments
118 */
119 private customizeDisconnectRequest(): void {
120 const originalRequest = this.nodeDebugSession.prototype.disconnectRequest;
121 const nodeDebugWrapper = this;
122
123 this.nodeDebugSession.prototype.disconnectRequest = function (response: any, args: any): void {
124 // First we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
125
126 if (nodeDebugWrapper.mobilePlatformOptions.platform === "android") {
127 nodeDebugWrapper.remoteExtension.stopMonitoringLogcat()
128 .catch(reason =>
129 Log.logError(`WARNING: Couldn't stop monitoring logcat: ${reason.message || reason}\n`))
130 .finally(() =>
131 originalRequest.call(this, response, args));
132 } else {
133 originalRequest.call(this, response, args);
134 }
135 };
136 }
137
138 /**
139 * Makes the required setup for request customization
140 * - Enables telemetry
141 * - Sets up mobilePlatformOptions, remote extension and projectRootPath
142 * - Starts debug server
143 * - Create global logger
144 */
145 private requestSetup(debugSession: NodeDebugSession, args: any) {
146 this.projectRootPath = path.resolve(args.program, "../..");
147 this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
148 this.mobilePlatformOptions = {
149 projectRoot: this.projectRootPath,
150 platform: args.platform,
151 };
152
153 // Start to send telemetry
154 this.telemetryReporter.reassignTo(new ExtensionTelemetryReporter(
155 this.appName, this.version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
156
157 // Create a server waiting for messages to re-initialize the debug session;
158 const debugServerListeningPort = this.createReinitializeServer(debugSession, args.internalDebuggerPort);
159 args.args = [debugServerListeningPort.toString()];
160
161 Log.SetGlobalLogger(new NodeDebugAdapterLogger(this.vscodeDebugAdapterPackage, debugSession));
162 }
163
164 /**
165 * Runs logic needed to attach.
166 * Attach should:
167 * - Enable js debugging
168 */
169 private attachRequest(debugSession: NodeDebugSession, request: any, args: any, mobilePlatform: any): Q.Promise<void> {
170 return TelemetryHelper.generate("attach", (generator) => {
171 return Q({})
172 .then(() => {
173 generator.step("mobilePlatform.enableJSDebuggingMode");
174 if (mobilePlatform) {
175 return mobilePlatform.enableJSDebuggingMode();
176 } else {
177 Log.logMessage("Debugger ready. Enable remote debugging in app.");
178 }
179 }).then(() =>
180 this.originalLaunchRequest.call(debugSession, request, args))
181 .catch(error =>
182 this.bailOut(debugSession, error.message));
183 });
184 }
185
186 /**
187 * Creates internal debug server and returns the port that the server is hook up into.
188 */
189 private createReinitializeServer(debugSession: NodeDebugSession, internalDebuggerPort: string): number {
190 // Create the server
191 const server = http.createServer((req, res) => {
192 res.statusCode = 404;
193 if (req.url === "/refreshBreakpoints") {
194 res.statusCode = 200;
195 if (debugSession) {
196 const sourceMaps = debugSession._sourceMaps;
197 if (sourceMaps) {
198 // Flush any cached source maps
199 sourceMaps._allSourceMaps = {};
200 sourceMaps._generatedToSourceMaps = {};
201 sourceMaps._sourceToGeneratedMaps = {};
202 }
203 // Send an "initialized" event to trigger breakpoints to be re-sent
204 debugSession.sendEvent(new this.vscodeDebugAdapterPackage.InitializedEvent());
205 }
206 }
207 res.end();
208 });
209
210 // Setup listen port and on error response
211 const port = parseInt(internalDebuggerPort, 10) || 9090;
212
213 server.listen(port);
214 server.on("error", (err: Error) => {
215 TelemetryHelper.sendSimpleEvent("reinitializeServerError");
216 Log.logError("Error in debug adapter server: " + err.toString());
217 Log.logMessage("Breakpoints may not update. Consider restarting and specifying a different 'internalDebuggerPort' in launch.json");
218 });
219
220 // Return listen port
221 return port;
222 }
223
224 /**
225 * Logs error to user and finishes the debugging process.
226 */
227 private bailOut(debugSession: NodeDebugSession, message: string): void {
228 Log.logError(`Could not debug. ${message}`);
229 debugSession.sendEvent(new this.vscodeDebugAdapterPackage.TerminatedEvent());
230 process.exit(1);
231 }
232
233 /**
234 * Parses log cat arguments to a string
235 */
236 private parseLogCatArguments(userProvidedLogCatArguments: any): string {
237 return Array.isArray(userProvidedLogCatArguments)
238 ? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
239 : userProvidedLogCatArguments; // If not, we leave it as-is
240 }
241
242 /**
243 * Helper method to know if a value is either null or undefined
244 */
245 private isNullOrUndefined(value: any): boolean {
246 return typeof value === "undefined" || value === null;
247 }
248}