microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
a4ab12aa376aabb9f2e998179021d3019e9385ae

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

272lines · modeblame

65bb0c85Jimmy Thomson10 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
40e4b177Patricio Beltran10 years ago4import * as Q from "q";
65bb0c85Jimmy Thomson10 years ago5import * as path from "path";
6import * as http from "http";
38edb09eAlexander Sorokin9 years ago7import * as fs from "fs";
8import stripJsonComments = require("strip-json-comments");
65bb0c85Jimmy Thomson10 years ago9
d976d077Meena Kunnathur Balakrishnan10 years ago10import {Telemetry} from "../common/telemetry";
dd442738Jimmy Thomson10 years ago11import {TelemetryHelper} from "../common/telemetryHelper";
725cf712digeff10 years ago12import {RemoteExtension} from "../common/remoteExtension";
bb77358cMark Oswald10 years ago13import {IOSPlatform} from "./ios/iOSPlatform";
40e4b177Patricio Beltran10 years ago14import {PlatformResolver} from "./platformResolver";
15import {IRunOptions} from "../common/launchArgs";
16import {TargetPlatformHelper} from "../common/targetPlatformHelper";
17import {ExtensionTelemetryReporter, ReassignableTelemetryReporter} from "../common/telemetryReporters";
18import {NodeDebugAdapterLogger} from "../common/log/loggers";
19import {Log} from "../common/log/log";
ac7fef0cPatricio Beltran9 years ago20import {GeneralMobilePlatform} from "../common/generalMobilePlatform";
40e4b177Patricio Beltran10 years ago21
22export class NodeDebugWrapper {
23private projectRootPath: string;
24private remoteExtension: RemoteExtension;
25private telemetryReporter: ReassignableTelemetryReporter;
26private appName: string;
27private version: string;
28private mobilePlatformOptions: IRunOptions;
29
30private vscodeDebugAdapterPackage: typeof VSCodeDebugAdapter;
31private nodeDebugSession: typeof NodeDebugSession;
774cffd7Vladimir Kotikov9 years ago32private sourceMapsConstructor: typeof SourceMaps;
40e4b177Patricio Beltran10 years ago33private originalLaunchRequest: (response: any, args: any) => void;
34
774cffd7Vladimir Kotikov9 years ago35public constructor(appName: string, version: string, telemetryReporter: ReassignableTelemetryReporter,
36debugAdapter: typeof VSCodeDebugAdapter, debugSession: typeof NodeDebugSession, sourceMaps: typeof SourceMaps) {
37
40e4b177Patricio Beltran10 years ago38this.appName = appName;
39this.version = version;
40this.telemetryReporter = telemetryReporter;
41this.vscodeDebugAdapterPackage = debugAdapter;
42this.nodeDebugSession = debugSession;
774cffd7Vladimir Kotikov9 years ago43this.sourceMapsConstructor = sourceMaps;
40e4b177Patricio Beltran10 years ago44this.originalLaunchRequest = this.nodeDebugSession.prototype.launchRequest;
65bb0c85Jimmy Thomson10 years ago45}
40e4b177Patricio Beltran10 years ago46
47/**
48* Calls customize methods for all requests needed
49*/
50public customizeNodeAdapterRequests(): void {
51this.customizeLaunchRequest();
52this.customizeAttachRequest();
53this.customizeDisconnectRequest();
65bb0c85Jimmy Thomson10 years ago54}
18d8ad2adigeff10 years ago55
40e4b177Patricio Beltran10 years ago56/**
57* Intecept the "launchRequest" instance method of NodeDebugSession to interpret arguments.
58* Launch should:
59* - Run the packager if needed
60* - Compile and run application
61* - Prewarm bundle
62*/
63private customizeLaunchRequest(): void {
64const nodeDebugWrapper = this;
65this.nodeDebugSession.prototype.launchRequest = function (request: any, args: ILaunchRequestArgs) {
66nodeDebugWrapper.requestSetup(this, args);
67nodeDebugWrapper.mobilePlatformOptions.target = args.target || "simulator";
68nodeDebugWrapper.mobilePlatformOptions.iosRelativeProjectPath = !nodeDebugWrapper.isNullOrUndefined(args.iosRelativeProjectPath) ?
69args.iosRelativeProjectPath :
70IOSPlatform.DEFAULT_IOS_PROJECT_RELATIVE_PATH;
71
72// We add the parameter if it's defined (adapter crashes otherwise)
73if (!nodeDebugWrapper.isNullOrUndefined(args.logCatArguments)) {
74nodeDebugWrapper.mobilePlatformOptions.logCatArguments = [nodeDebugWrapper.parseLogCatArguments(args.logCatArguments)];
75}
18d8ad2adigeff10 years ago76
40e4b177Patricio Beltran10 years ago77return TelemetryHelper.generate("launch", (generator) => {
78const resolver = new PlatformResolver();
79return nodeDebugWrapper.remoteExtension.getPackagerPort()
80.then(packagerPort => {
81nodeDebugWrapper.mobilePlatformOptions.packagerPort = packagerPort;
82const mobilePlatform = resolver.resolveMobilePlatform(args.platform, nodeDebugWrapper.mobilePlatformOptions);
299b0557Patricio Beltran10 years ago83return Q({})
84.then(() => {
85generator.step("checkPlatformCompatibility");
86TargetPlatformHelper.checkTargetPlatformSupport(nodeDebugWrapper.mobilePlatformOptions.platform);
87generator.step("startPackager");
88return mobilePlatform.startPackager();
89})
90.then(() => {
91// 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
92// and the user needs to Reload JS manually. We prewarm it to prevent that issue
93generator.step("prewarmBundleCache");
94Log.logMessage("Prewarming bundle cache. This may take a while ...");
95return mobilePlatform.prewarmBundleCache();
96})
97.then(() => {
98generator.step("mobilePlatform.runApp");
99Log.logMessage("Building and running application.");
100return mobilePlatform.runApp();
101})
102.then(() =>
103nodeDebugWrapper.attachRequest(this, request, args, mobilePlatform));
40e4b177Patricio Beltran10 years ago104}).catch(error =>
105nodeDebugWrapper.bailOut(this, error.message));
18d8ad2adigeff10 years ago106});
40e4b177Patricio Beltran10 years ago107};
108}
18d8ad2adigeff10 years ago109
40e4b177Patricio Beltran10 years ago110/**
111* Intecept the "attachRequest" instance method of NodeDebugSession to interpret arguments
112*/
113private customizeAttachRequest(): void {
114const nodeDebugWrapper = this;
115this.nodeDebugSession.prototype.attachRequest = function (request: any, args: IAttachRequestArgs) {
116nodeDebugWrapper.requestSetup(this, args);
299b0557Patricio Beltran10 years ago117nodeDebugWrapper.attachRequest(this, request, args, new GeneralMobilePlatform(nodeDebugWrapper.mobilePlatformOptions));
40e4b177Patricio Beltran10 years ago118};
119}
c96fc63eMark Oswald10 years ago120
40e4b177Patricio Beltran10 years ago121/**
122* Intecept the "disconnectRequest" instance method of NodeDebugSession to interpret arguments
123*/
124private customizeDisconnectRequest(): void {
125const originalRequest = this.nodeDebugSession.prototype.disconnectRequest;
126const nodeDebugWrapper = this;
127
128this.nodeDebugSession.prototype.disconnectRequest = function (response: any, args: any): void {
129// First we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
130
131if (nodeDebugWrapper.mobilePlatformOptions.platform === "android") {
132nodeDebugWrapper.remoteExtension.stopMonitoringLogcat()
133.catch(reason =>
134Log.logError(`WARNING: Couldn't stop monitoring logcat: ${reason.message || reason}\n`))
135.finally(() =>
136originalRequest.call(this, response, args));
137} else {
138originalRequest.call(this, response, args);
4881129dMeena Kunnathur Balakrishnan10 years ago139}
40e4b177Patricio Beltran10 years ago140};
141}
710f8655digeff10 years ago142
40e4b177Patricio Beltran10 years ago143/**
144* Makes the required setup for request customization
145* - Enables telemetry
146* - Sets up mobilePlatformOptions, remote extension and projectRootPath
147* - Starts debug server
148* - Create global logger
149*/
150private requestSetup(debugSession: NodeDebugSession, args: any) {
38edb09eAlexander Sorokin9 years ago151this.projectRootPath = this.getProjectRoot(args);
40e4b177Patricio Beltran10 years ago152this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
153this.mobilePlatformOptions = {
154projectRoot: this.projectRootPath,
155platform: args.platform,
18d8ad2adigeff10 years ago156};
157
40e4b177Patricio Beltran10 years ago158// Start to send telemetry
159this.telemetryReporter.reassignTo(new ExtensionTelemetryReporter(
160this.appName, this.version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
161
162// Create a server waiting for messages to re-initialize the debug session;
774cffd7Vladimir Kotikov9 years ago163const debugServerListeningPort = this.createReinitializeServer(debugSession, args.internalDebuggerPort, args.outDir);
40e4b177Patricio Beltran10 years ago164args.args = [debugServerListeningPort.toString()];
165
166Log.SetGlobalLogger(new NodeDebugAdapterLogger(this.vscodeDebugAdapterPackage, debugSession));
167}
168
169/**
170* Runs logic needed to attach.
171* Attach should:
172* - Enable js debugging
173*/
174private attachRequest(debugSession: NodeDebugSession, request: any, args: any, mobilePlatform: any): Q.Promise<void> {
175return TelemetryHelper.generate("attach", (generator) => {
176return Q({})
177.then(() => {
178generator.step("mobilePlatform.enableJSDebuggingMode");
179if (mobilePlatform) {
180return mobilePlatform.enableJSDebuggingMode();
181} else {
182Log.logMessage("Debugger ready. Enable remote debugging in app.");
183}
184}).then(() =>
185this.originalLaunchRequest.call(debugSession, request, args))
186.catch(error =>
187this.bailOut(debugSession, error.message));
188});
189}
190
191/**
192* Creates internal debug server and returns the port that the server is hook up into.
193*/
774cffd7Vladimir Kotikov9 years ago194private createReinitializeServer(debugSession: NodeDebugSession, internalDebuggerPort: string, sourcesDir: string): number {
40e4b177Patricio Beltran10 years ago195// Create the server
196const server = http.createServer((req, res) => {
197res.statusCode = 404;
198if (req.url === "/refreshBreakpoints") {
199res.statusCode = 200;
200if (debugSession) {
201const sourceMaps = debugSession._sourceMaps;
202if (sourceMaps) {
203// Flush any cached source maps
774cffd7Vladimir Kotikov9 years ago204// Rather than cleaning internal caches we recreate
205// SourceMaps to add downloaded bundle map to cache
8f87e135Vladimir Kotikov9 years ago206const bundlePattern = path.join(sourcesDir, "*.bundle");
774cffd7Vladimir Kotikov9 years ago207const sourceMaps = new this.sourceMapsConstructor(debugSession, sourcesDir, [bundlePattern]);
208debugSession._sourceMaps = sourceMaps;
40e4b177Patricio Beltran10 years ago209}
210// Send an "initialized" event to trigger breakpoints to be re-sent
211debugSession.sendEvent(new this.vscodeDebugAdapterPackage.InitializedEvent());
212}
4881129dMeena Kunnathur Balakrishnan10 years ago213}
40e4b177Patricio Beltran10 years ago214res.end();
215});
216
217// Setup listen port and on error response
218const port = parseInt(internalDebuggerPort, 10) || 9090;
c2bf3c4fdigeff10 years ago219
40e4b177Patricio Beltran10 years ago220server.listen(port);
221server.on("error", (err: Error) => {
222TelemetryHelper.sendSimpleEvent("reinitializeServerError");
223Log.logError("Error in debug adapter server: " + err.toString());
224Log.logMessage("Breakpoints may not update. Consider restarting and specifying a different 'internalDebuggerPort' in launch.json");
225});
226
227// Return listen port
228return port;
229}
230
231/**
232* Logs error to user and finishes the debugging process.
233*/
234private bailOut(debugSession: NodeDebugSession, message: string): void {
235Log.logError(`Could not debug. ${message}`);
236debugSession.sendEvent(new this.vscodeDebugAdapterPackage.TerminatedEvent());
237process.exit(1);
238}
239
240/**
241* Parses log cat arguments to a string
242*/
243private parseLogCatArguments(userProvidedLogCatArguments: any): string {
244return Array.isArray(userProvidedLogCatArguments)
245? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
246: userProvidedLogCatArguments; // If not, we leave it as-is
247}
248
249/**
250* Helper method to know if a value is either null or undefined
251*/
252private isNullOrUndefined(value: any): boolean {
253return typeof value === "undefined" || value === null;
254}
38edb09eAlexander Sorokin9 years ago255
256/**
257* Parses settings.json file for workspace root property
258*/
259private getProjectRoot(args: any): string {
260try {
261let vsCodeRoot = path.resolve(args.program, "../..");
262let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
263let settingsContent = fs.readFileSync(settingsPath, "utf8");
264settingsContent = stripJsonComments(settingsContent);
265let parsedSettings = JSON.parse(settingsContent);
266let projectRootPath = parsedSettings["react-native-tools"].projectRoot;
267return path.resolve(vsCodeRoot, projectRootPath);
268} catch (e) {
269return path.resolve(args.program, "../..");
270}
271}
774cffd7Vladimir Kotikov9 years ago272}