microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
8f87e13531e1fc4465733c748fd76d4ae5710eaa

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

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