microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7e74daf7bcd1879274fba2873d9d52d0f78e2122

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/rnDebugSession.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 vscode from "vscode";
5import * as Q from "q";
6import * as path from "path";
7import * as fs from "fs";
8import * as mkdirp from "mkdirp";
9import stripJsonComments = require("strip-json-comments");
10import { LoggingDebugSession, Logger, logger } from "vscode-debugadapter";
11import { DebugProtocol } from "vscode-debugprotocol";
12import { getLoggingDirectory, LogHelper } from "../extension/log/LogHelper";
13import { ReactNativeProjectHelper } from "../common/reactNativeProjectHelper";
14import { ErrorHelper } from "../common/error/errorHelper";
15import { InternalErrorCode } from "../common/error/internalErrorCode";
16import { ILaunchArgs } from "../extension/launchArgs";
17import { ProjectVersionHelper } from "../common/projectVersionHelper";
18import { TelemetryHelper } from "../common/telemetryHelper";
19import { AppLauncher } from "../extension/appLauncher";
20import { MultipleLifetimesAppWorker } from "./appWorker";
21import { ReactNativeCDPProxy } from "../cdp-proxy/reactNativeCDPProxy";
22import { generateRandomPortNumber } from "../common/extensionHelper";
23import { LogLevel } from "../extension/log/LogHelper";
24import * as nls from "vscode-nls";
25const localize = nls.loadMessageBundle();
26
27export interface IAttachRequestArgs extends DebugProtocol.AttachRequestArguments, ILaunchArgs {
28 cwd: string; /* Automatically set by VS Code to the currently opened folder */
29 port: number;
30 url?: string;
31 address?: string;
32 trace?: string;
33}
34
35export interface ILaunchRequestArgs extends DebugProtocol.LaunchRequestArguments, IAttachRequestArgs { }
36
37export class RNDebugSession extends LoggingDebugSession {
38
39 private readonly cdpProxyPort: number;
40 private readonly cdpProxyHostAddress: string;
41
42 private appLauncher: AppLauncher;
43 private appWorker: MultipleLifetimesAppWorker | null;
44 private projectRootPath: string;
45 private isSettingsInitialized: boolean; // used to prevent parameters reinitialization when attach is called from launch function
46 private previousAttachArgs: IAttachRequestArgs;
47 private rnCdpProxy: ReactNativeCDPProxy | null;
48 private cdpProxyLogLevel: LogLevel;
49
50 constructor(private session: vscode.DebugSession) {
51 super();
52 this.isSettingsInitialized = false;
53 this.appWorker = null;
54 this.rnCdpProxy = null;
55 this.cdpProxyPort = generateRandomPortNumber();
56 this.cdpProxyHostAddress = "127.0.0.1";
57 }
58
59 protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
60 super.initializeRequest(response, args);
61 }
62
63 protected launchRequest(response: DebugProtocol.LaunchResponse, launchArgs: ILaunchRequestArgs, request?: DebugProtocol.Request): Promise<void> {
64 return new Promise<void>((resolve, reject) => this.initializeSettings(launchArgs)
65 .then(() => {
66 logger.log("Launching the application");
67 logger.verbose(`Launching the application: ${JSON.stringify(launchArgs, null , 2)}`);
68
69 this.appLauncher.launch(launchArgs)
70 .then(() => {
71 return this.appLauncher.getPackagerPort(launchArgs.cwd);
72 })
73 .then((packagerPort: number) => {
74 launchArgs.port = launchArgs.port || packagerPort;
75 this.attachRequest(response, launchArgs).then(() => {
76 resolve();
77 }).catch((e) => reject(e));
78 })
79 .catch((err) => {
80 logger.error("An error occurred while attaching to the debugger. " + err.message || err);
81 reject(err);
82 });
83 }));
84 }
85
86 protected attachRequest(response: DebugProtocol.AttachResponse, attachArgs: IAttachRequestArgs, request?: DebugProtocol.Request): Promise<void> {
87 let extProps = {
88 platform: {
89 value: attachArgs.platform,
90 isPii: false,
91 },
92 };
93
94 this.previousAttachArgs = attachArgs;
95 return new Promise<void>((resolve, reject) => this.initializeSettings(attachArgs)
96 .then(() => {
97 logger.log("Attaching to the application");
98 logger.verbose(`Attaching to the application: ${JSON.stringify(attachArgs, null , 2)}`);
99 return ProjectVersionHelper.getReactNativeVersions(attachArgs.cwd, true)
100 .then(versions => {
101 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeVersion, "reactNativeVersion", extProps);
102 if (!ProjectVersionHelper.isVersionError(versions.reactNativeWindowsVersion)) {
103 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeWindowsVersion, "reactNativeWindowsVersion", extProps);
104 }
105 return TelemetryHelper.generate("attach", extProps, (generator) => {
106 this.rnCdpProxy = new ReactNativeCDPProxy(this.cdpProxyHostAddress, this.cdpProxyPort, this.cdpProxyLogLevel);
107 attachArgs.port = attachArgs.port || this.appLauncher.getPackagerPort(attachArgs.cwd);
108 return this.rnCdpProxy.createServer()
109 .then(() => {
110 logger.log(localize("StartingDebuggerAppWorker", "Starting debugger app worker."));
111
112 const sourcesStoragePath = path.join(this.projectRootPath, ".vscode", ".react");
113 // Create folder if not exist to avoid problems if
114 // RN project root is not a ${workspaceFolder}
115 mkdirp.sync(sourcesStoragePath);
116
117 // If launch is invoked first time, appWorker is undefined, so create it here
118 this.appWorker = new MultipleLifetimesAppWorker(
119 attachArgs,
120 sourcesStoragePath,
121 this.projectRootPath,
122 undefined
123 );
124 this.appLauncher.setAppWorker(this.appWorker);
125
126 this.appWorker.on("connected", (port: number) => {
127 logger.log(localize("DebuggerWorkerLoadedRuntimeOnPort", "Debugger worker loaded runtime on port {0}", port));
128
129 if (this.rnCdpProxy) {
130 this.rnCdpProxy.setApplicationTargetPort(port);
131
132 const attachArguments = {
133 type: "pwa-node",
134 request: "attach",
135 name: "Attach",
136 continueOnAttach: true,
137 port: this.cdpProxyPort,
138 smartStep: false,
139 };
140
141 vscode.debug.startDebugging(
142 this.appLauncher.getWorkspaceFolder(),
143 attachArguments,
144 this.session
145 )
146 .then((childDebugSessionStarted: boolean) => {
147 if (childDebugSessionStarted) {
148 resolve();
149 } else {
150 reject(new Error("Cannot start child debug session"));
151 }
152 },
153 err => {
154 reject(err);
155 });
156 } else {
157 throw new Error("Cannot connect to debugger worker: Chrome debugger proxy is offline");
158 }
159 });
160 return this.appWorker.start();
161 });
162 })
163 .catch((err) => {
164 logger.error("An error occurred while attaching to the debugger. " + err.message || err);
165 reject(err);
166 });
167 });
168 }));
169 }
170
171 protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments, request?: DebugProtocol.Request): void {
172 // The client is about to disconnect so first we need to stop app worker
173 if (this.appWorker) {
174 this.appWorker.stop();
175 }
176
177 if (this.rnCdpProxy) {
178 this.rnCdpProxy.stopServer();
179 this.rnCdpProxy = null;
180 }
181
182 // Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
183 if (this.previousAttachArgs.platform === "android") {
184 try {
185 this.appLauncher.stopMonitoringLogCat();
186 } catch (err) {
187 logger.warn(localize("CouldNotStopMonitoringLogcat", "Couldn't stop monitoring logcat: {0}", err.message || err));
188 }
189 }
190
191 super.disconnectRequest(response, args, request);
192 }
193
194 private initializeSettings(args: any): Q.Promise<any> {
195 if (!this.isSettingsInitialized) {
196 let chromeDebugCoreLogs = getLoggingDirectory();
197 if (chromeDebugCoreLogs) {
198 chromeDebugCoreLogs = path.join(chromeDebugCoreLogs, "ChromeDebugCoreLogs.txt");
199 }
200 let logLevel: string = args.trace;
201 if (logLevel) {
202 logLevel = logLevel.replace(logLevel[0], logLevel[0].toUpperCase());
203 logger.setup(Logger.LogLevel[logLevel], chromeDebugCoreLogs || false);
204 this.cdpProxyLogLevel = LogLevel[logLevel] === LogLevel.Verbose ? LogLevel.Custom : LogLevel.None;
205 } else {
206 logger.setup(Logger.LogLevel.Log, chromeDebugCoreLogs || false);
207 this.cdpProxyLogLevel = LogHelper.LOG_LEVEL === LogLevel.Trace ? LogLevel.Custom : LogLevel.None;
208 }
209
210 if (!args.sourceMaps) {
211 args.sourceMaps = true;
212 }
213
214 const projectRootPath = getProjectRoot(args);
215 return ReactNativeProjectHelper.isReactNativeProject(projectRootPath)
216 .then((result) => {
217 if (!result) {
218 throw ErrorHelper.getInternalError(InternalErrorCode.NotInReactNativeFolderError);
219 }
220 this.projectRootPath = projectRootPath;
221 this.appLauncher = AppLauncher.getAppLauncherByProjectRootPath(projectRootPath);
222 this.isSettingsInitialized = true;
223
224 return void 0;
225 });
226 } else {
227 return Q.resolve<void>(void 0);
228 }
229 }
230}
231
232/**
233 * Parses settings.json file for workspace root property
234 */
235export function getProjectRoot(args: any): string {
236 const vsCodeRoot = args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
237 const settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
238 try {
239 let settingsContent = fs.readFileSync(settingsPath, "utf8");
240 settingsContent = stripJsonComments(settingsContent);
241 let parsedSettings = JSON.parse(settingsContent);
242 let projectRootPath = parsedSettings["react-native-tools.projectRoot"] || parsedSettings["react-native-tools"].projectRoot;
243 return path.resolve(vsCodeRoot, projectRootPath);
244 } catch (e) {
245 logger.verbose(`${settingsPath} file doesn't exist or its content is incorrect. This file will be ignored.`);
246 return args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
247 }
248}