microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
5940f996e19e33b2c611c0d447a7ffb66d249d80

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/debugSessionBase.ts

242lines · 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 path from "path";
6import * as fs from "fs";
7import stripJsonComments = require("strip-json-comments");
8import { LoggingDebugSession, Logger, logger, ErrorDestination } from "vscode-debugadapter";
9import { DebugProtocol } from "vscode-debugprotocol";
10import { getLoggingDirectory, LogHelper } from "../extension/log/LogHelper";
11import { ReactNativeProjectHelper } from "../common/reactNativeProjectHelper";
12import { ErrorHelper } from "../common/error/errorHelper";
13import { InternalErrorCode } from "../common/error/internalErrorCode";
14import { InternalError, NestedError } from "../common/error/internalError";
15import { IRunOptions, PlatformType } from "../extension/launchArgs";
16import { AppLauncher } from "../extension/appLauncher";
17import { LogLevel } from "../extension/log/LogHelper";
18import * as nls from "vscode-nls";
19nls.config({
20 messageFormat: nls.MessageFormat.bundle,
21 bundleFormat: nls.BundleFormat.standalone,
22})();
23const localize = nls.loadMessageBundle();
24
25/**
26 * Enum of possible statuses of debug session
27 */
28export enum DebugSessionStatus {
29 /** A session has been just created */
30 FirstConnection,
31 /** This status is required in order to exclude the possible creation of several debug sessions at the first start */
32 FirstConnectionPending,
33 /** This status means that an application can be reloaded */
34 ConnectionAllowed,
35 /** This status means that an application is reloading now, and we shouldn't terminate the current debug session */
36 ConnectionPending,
37 /** A debuggee connected successfully */
38 ConnectionDone,
39 /** A debuggee failed to connect */
40 ConnectionFailed,
41}
42
43export interface TerminateEventArgs {
44 debugSession: vscode.DebugSession;
45 args: any;
46}
47
48export interface IAttachRequestArgs
49 extends DebugProtocol.AttachRequestArguments,
50 IRunOptions,
51 vscode.DebugConfiguration {
52 webkitRangeMax: number;
53 webkitRangeMin: number;
54 cwd: string /* Automatically set by VS Code to the currently opened folder */;
55 port: number;
56 url?: string;
57 address?: string;
58 trace?: string;
59 skipFiles?: [];
60 sourceMaps?: boolean;
61 sourceMapPathOverrides?: { [key: string]: string };
62}
63
64export interface ILaunchRequestArgs
65 extends DebugProtocol.LaunchRequestArguments,
66 IAttachRequestArgs {}
67
68export abstract class DebugSessionBase extends LoggingDebugSession {
69 protected static rootSessionTerminatedEventEmitter: vscode.EventEmitter<TerminateEventArgs> = new vscode.EventEmitter<TerminateEventArgs>();
70 public static readonly onDidTerminateRootDebugSession =
71 DebugSessionBase.rootSessionTerminatedEventEmitter.event;
72
73 protected readonly stopCommand: string;
74 protected readonly pwaNodeSessionName: string;
75
76 protected appLauncher: AppLauncher;
77 protected projectRootPath: string;
78 protected isSettingsInitialized: boolean; // used to prevent parameters reinitialization when attach is called from launch function
79 protected previousAttachArgs: IAttachRequestArgs;
80 protected cdpProxyLogLevel: LogLevel;
81 protected debugSessionStatus: DebugSessionStatus;
82 protected session: vscode.DebugSession;
83 protected cancellationTokenSource: vscode.CancellationTokenSource;
84
85 constructor(session: vscode.DebugSession) {
86 super();
87
88 // constants definition
89 this.pwaNodeSessionName = "pwa-node"; // the name of node debug session created by js-debug extension
90 this.stopCommand = "workbench.action.debug.stop"; // the command which simulates a click on the "Stop" button
91
92 // variables definition
93 this.session = session;
94 this.isSettingsInitialized = false;
95 this.debugSessionStatus = DebugSessionStatus.FirstConnection;
96 this.cancellationTokenSource = new vscode.CancellationTokenSource();
97 }
98
99 protected initializeRequest(
100 response: DebugProtocol.InitializeResponse,
101 // eslint-disable-next-line @typescript-eslint/no-unused-vars
102 args: DebugProtocol.InitializeRequestArguments,
103 ): void {
104 response.body = response.body || {};
105
106 response.body.supportsConfigurationDoneRequest = true;
107 response.body.supportsEvaluateForHovers = true;
108 response.body.supportTerminateDebuggee = true;
109 response.body.supportsCancelRequest = true;
110
111 this.sendResponse(response);
112 }
113
114 protected abstract establishDebugSession(
115 attachArgs: IAttachRequestArgs,
116 resolve?: (value?: void | PromiseLike<void> | undefined) => void,
117 ): void;
118
119 protected initializeSettings(args: any): Promise<any> {
120 if (!this.isSettingsInitialized) {
121 let chromeDebugCoreLogs = getLoggingDirectory();
122 if (chromeDebugCoreLogs) {
123 chromeDebugCoreLogs = path.join(chromeDebugCoreLogs, "DebugSessionLogs.txt");
124 }
125 let logLevel: string = args.trace;
126 if (logLevel) {
127 logLevel = logLevel.replace(logLevel[0], logLevel[0].toUpperCase());
128 logger.setup(Logger.LogLevel[logLevel], chromeDebugCoreLogs || false);
129 this.cdpProxyLogLevel =
130 LogLevel[logLevel] === LogLevel.Verbose ? LogLevel.Custom : LogLevel.None;
131 } else {
132 logger.setup(Logger.LogLevel.Log, chromeDebugCoreLogs || false);
133 this.cdpProxyLogLevel =
134 LogHelper.LOG_LEVEL === LogLevel.Trace ? LogLevel.Custom : LogLevel.None;
135 }
136
137 if (typeof args.sourceMaps !== "boolean") {
138 args.sourceMaps = true;
139 }
140
141 if (typeof args.enableDebug !== "boolean") {
142 args.enableDebug = true;
143 }
144
145 const projectRootPath = getProjectRoot(args);
146 return ReactNativeProjectHelper.isReactNativeProject(projectRootPath).then(result => {
147 if (!result) {
148 throw ErrorHelper.getInternalError(
149 InternalErrorCode.NotInReactNativeFolderError,
150 );
151 }
152 this.projectRootPath = projectRootPath;
153 this.appLauncher = AppLauncher.getAppLauncherByProjectRootPath(projectRootPath);
154 this.isSettingsInitialized = true;
155
156 return void 0;
157 });
158 } else {
159 return Promise.resolve();
160 }
161 }
162
163 protected async disconnectRequest(
164 response: DebugProtocol.DisconnectResponse,
165 args: DebugProtocol.DisconnectArguments,
166 // eslint-disable-next-line @typescript-eslint/no-unused-vars
167 request?: DebugProtocol.Request,
168 ): Promise<void> {
169 if (this.appLauncher) {
170 await this.appLauncher.getRnCdpProxy().stopServer();
171 }
172
173 this.cancellationTokenSource.cancel();
174 this.cancellationTokenSource.dispose();
175
176 // Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
177 if (this.previousAttachArgs && this.previousAttachArgs.platform === PlatformType.Android) {
178 try {
179 this.appLauncher.getMobilePlatform().dispose();
180 } catch (err) {
181 logger.warn(
182 localize(
183 "CouldNotStopMonitoringLogcat",
184 "Couldn't stop monitoring logcat: {0}",
185 err.message || err,
186 ),
187 );
188 }
189 }
190
191 await logger.dispose();
192
193 DebugSessionBase.rootSessionTerminatedEventEmitter.fire({
194 debugSession: this.session,
195 args: {
196 forcedStop: !!(<any>args).forcedStop,
197 },
198 });
199
200 this.sendResponse(response);
201 }
202
203 protected showError(error: Error, response: DebugProtocol.Response): void {
204 // We can't print error messages after the debugging session is stopped. This could break the extension work.
205 if (
206 (error instanceof InternalError || error instanceof NestedError) &&
207 error.errorCode === InternalErrorCode.CancellationTokenTriggered
208 ) {
209 return;
210 }
211
212 this.sendErrorResponse(
213 response,
214 { format: error.message, id: 1 },
215 undefined,
216 undefined,
217 ErrorDestination.User,
218 );
219 }
220}
221
222/**
223 * Parses settings.json file for workspace root property
224 */
225export function getProjectRoot(args: any): string {
226 const vsCodeRoot = args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
227 const settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
228 try {
229 let settingsContent = fs.readFileSync(settingsPath, "utf8");
230 settingsContent = stripJsonComments(settingsContent);
231 let parsedSettings = JSON.parse(settingsContent);
232 let projectRootPath =
233 parsedSettings["react-native-tools.projectRoot"] ||
234 parsedSettings["react-native-tools"].projectRoot;
235 return path.resolve(vsCodeRoot, projectRootPath);
236 } catch (e) {
237 logger.verbose(
238 `${settingsPath} file doesn't exist or its content is incorrect. This file will be ignored.`,
239 );
240 return args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
241 }
242}
243