microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.12.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/rnDebugSession.ts

312lines · 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 path from "path";
5import * as vscode from "vscode";
6import * as mkdirp from "mkdirp";
7import { logger } from "@vscode/debugadapter";
8import { DebugProtocol } from "vscode-debugprotocol";
9import * as nls from "vscode-nls";
10import { ProjectVersionHelper } from "../common/projectVersionHelper";
11import { TelemetryHelper } from "../common/telemetryHelper";
12import { RnCDPMessageHandler } from "../cdp-proxy/CDPMessageHandlers/rnCDPMessageHandler";
13import { ErrorHelper } from "../common/error/errorHelper";
14import { InternalErrorCode } from "../common/error/internalErrorCode";
15import { ReactNativeProjectHelper } from "../common/reactNativeProjectHelper";
16import { ExponentHelper } from "../extension/exponent/exponentHelper";
17import { MultipleLifetimesAppWorker } from "./appWorker";
18import {
19 DebugSessionBase,
20 DebugSessionStatus,
21 IAttachRequestArgs,
22 ILaunchRequestArgs,
23} from "./debugSessionBase";
24import { JsDebugConfigAdapter } from "./jsDebugConfigAdapter";
25import { RNSession } from "./debugSessionWrapper";
26
27nls.config({
28 messageFormat: nls.MessageFormat.bundle,
29 bundleFormat: nls.BundleFormat.standalone,
30})();
31const localize = nls.loadMessageBundle();
32
33export class RNDebugSession extends DebugSessionBase {
34 private appWorker: MultipleLifetimesAppWorker | null;
35 private onDidStartDebugSessionHandler: vscode.Disposable;
36 private onDidTerminateDebugSessionHandler: vscode.Disposable;
37
38 constructor(rnSession: RNSession) {
39 super(rnSession);
40
41 // variables definition
42 this.appWorker = null;
43
44 this.onDidStartDebugSessionHandler = vscode.debug.onDidStartDebugSession(
45 this.handleStartDebugSession.bind(this),
46 );
47
48 this.onDidTerminateDebugSessionHandler = vscode.debug.onDidTerminateDebugSession(
49 this.handleTerminateDebugSession.bind(this),
50 );
51 }
52
53 protected async launchRequest(
54 response: DebugProtocol.LaunchResponse,
55 launchArgs: ILaunchRequestArgs,
56 // eslint-disable-next-line @typescript-eslint/no-unused-vars
57 request?: DebugProtocol.Request,
58 ): Promise<void> {
59 try {
60 try {
61 if (launchArgs.platform != "exponent") {
62 await ReactNativeProjectHelper.verifyMetroConfigFile(launchArgs.cwd);
63 }
64 await this.initializeSettings(launchArgs);
65 logger.log("Launching the application");
66 logger.verbose(`Launching the application: ${JSON.stringify(launchArgs, null, 2)}`);
67
68 await this.appLauncher.launch(launchArgs);
69
70 if (!launchArgs.enableDebug) {
71 this.sendResponse(response);
72 // if debugging is not enabled skip attach request
73 return;
74 }
75 } catch (error) {
76 throw ErrorHelper.getInternalError(
77 InternalErrorCode.ApplicationLaunchFailed,
78 error.message || error,
79 );
80 }
81 // if debugging is enabled start attach request
82 await this.vsCodeDebugSession.customRequest("attach", launchArgs);
83 this.sendResponse(response);
84 } catch (error) {
85 this.terminateWithErrorResponse(error, response);
86 }
87 }
88
89 protected async attachRequest(
90 response: DebugProtocol.AttachResponse,
91 attachArgs: IAttachRequestArgs,
92 // eslint-disable-next-line @typescript-eslint/no-unused-vars
93 request?: DebugProtocol.Request,
94 ): Promise<void> {
95 let extProps = {
96 platform: {
97 value: attachArgs.platform,
98 isPii: false,
99 },
100 };
101
102 this.previousAttachArgs = attachArgs;
103
104 return new Promise<void>(async (resolve, reject) => {
105 try {
106 if (attachArgs.request === "attach") {
107 const expoHelper = new ExponentHelper(attachArgs.cwd, attachArgs.cwd);
108 const isExpo = await expoHelper.isExpoManagedApp(true);
109 if (!isExpo) {
110 await ReactNativeProjectHelper.verifyMetroConfigFile(attachArgs.cwd);
111 }
112 }
113 await this.initializeSettings(attachArgs);
114 logger.log("Attaching to the application");
115 logger.verbose(
116 `Attaching to the application: ${JSON.stringify(attachArgs, null, 2)}`,
117 );
118
119 const versions = await ProjectVersionHelper.getReactNativeVersions(
120 this.projectRootPath,
121 ProjectVersionHelper.generateAdditionalPackagesToCheckByPlatform(attachArgs),
122 );
123 extProps = TelemetryHelper.addPlatformPropertiesToTelemetryProperties(
124 attachArgs,
125 versions,
126 extProps,
127 );
128
129 // eslint-disable-next-line @typescript-eslint/no-unused-vars
130 await TelemetryHelper.generate("attach", extProps, async generator => {
131 attachArgs.port =
132 attachArgs.port || this.appLauncher.getPackagerPort(attachArgs.cwd);
133
134 const cdpProxy = this.appLauncher.getRnCdpProxy();
135 await cdpProxy.stopServer();
136 await cdpProxy.initializeServer(
137 new RnCDPMessageHandler(),
138 this.cdpProxyLogLevel,
139 this.cancellationTokenSource.token,
140 );
141
142 if (attachArgs.request === "attach") {
143 await this.preparePackagerBeforeAttach(attachArgs, versions);
144 }
145
146 logger.log(
147 localize("StartingDebuggerAppWorker", "Starting debugger app worker."),
148 );
149
150 const sourcesStoragePath = path.join(this.projectRootPath, ".vscode", ".react");
151 // Create folder if not exist to avoid problems if
152 // RN project root is not a ${workspaceFolder}
153 mkdirp.sync(sourcesStoragePath);
154
155 // If launch is invoked first time, appWorker is undefined, so create it here
156 this.appWorker = new MultipleLifetimesAppWorker(
157 attachArgs,
158 sourcesStoragePath,
159 this.projectRootPath,
160 this.cancellationTokenSource.token,
161 undefined,
162 );
163 this.appLauncher.setAppWorker(this.appWorker);
164
165 this.appWorker.on("connected", (port: number) => {
166 if (this.cancellationTokenSource.token.isCancellationRequested) {
167 return this.appWorker?.stop();
168 }
169
170 logger.log(
171 localize(
172 "DebuggerWorkerLoadedRuntimeOnPort",
173 "Debugger worker loaded runtime on port {0}",
174 port,
175 ),
176 );
177
178 cdpProxy.setApplicationTargetPort(port);
179
180 if (this.debugSessionStatus === DebugSessionStatus.ConnectionPending) {
181 return;
182 }
183
184 if (this.debugSessionStatus === DebugSessionStatus.FirstConnection) {
185 this.debugSessionStatus = DebugSessionStatus.FirstConnectionPending;
186 this.establishDebugSession(attachArgs, resolve);
187 } else if (
188 this.debugSessionStatus === DebugSessionStatus.ConnectionAllowed
189 ) {
190 if (this.nodeSession) {
191 this.debugSessionStatus = DebugSessionStatus.ConnectionPending;
192 void this.nodeSession.customRequest(this.terminateCommand);
193 }
194 }
195 });
196
197 if (this.cancellationTokenSource.token.isCancellationRequested) {
198 return this.appWorker.stop();
199 }
200 return await this.appWorker.start();
201 });
202 } catch (error) {
203 reject(error);
204 }
205 })
206 .then(() => {
207 this.sendResponse(response);
208 })
209 .catch(err =>
210 this.terminateWithErrorResponse(
211 ErrorHelper.getInternalError(
212 InternalErrorCode.CouldNotAttachToDebugger,
213 err.message || err,
214 ),
215 response,
216 ),
217 );
218 }
219
220 protected async disconnectRequest(
221 response: DebugProtocol.DisconnectResponse,
222 args: DebugProtocol.DisconnectArguments,
223 request?: DebugProtocol.Request,
224 ): Promise<void> {
225 // The client is about to disconnect so first we need to stop app worker
226 if (this.appWorker) {
227 this.appWorker.stop();
228 }
229
230 this.onDidStartDebugSessionHandler.dispose();
231 this.onDidTerminateDebugSessionHandler.dispose();
232
233 return super.disconnectRequest(response, args, request);
234 }
235
236 protected establishDebugSession(
237 attachArgs: IAttachRequestArgs,
238 resolve?: (value?: void | PromiseLike<void> | undefined) => void,
239 ): void {
240 const attachConfiguration = JsDebugConfigAdapter.createDebuggingConfigForPureRN(
241 attachArgs,
242 this.appLauncher.getCdpProxyPort(),
243 this.rnSession.sessionId,
244 );
245
246 vscode.debug
247 .startDebugging(this.appLauncher.getWorkspaceFolder(), attachConfiguration, {
248 parentSession: this.vsCodeDebugSession,
249 consoleMode: vscode.DebugConsoleMode.MergeWithParent,
250 })
251 .then(
252 (childDebugSessionStarted: boolean) => {
253 if (childDebugSessionStarted) {
254 this.debugSessionStatus = DebugSessionStatus.ConnectionDone;
255 this.setConnectionAllowedIfPossible();
256 if (resolve) {
257 this.debugSessionStatus = DebugSessionStatus.ConnectionAllowed;
258 resolve();
259 }
260 } else {
261 this.debugSessionStatus = DebugSessionStatus.ConnectionFailed;
262 this.setConnectionAllowedIfPossible();
263 this.resetFirstConnectionStatus();
264 throw new Error("Cannot start child debug session");
265 }
266 },
267 err => {
268 this.debugSessionStatus = DebugSessionStatus.ConnectionFailed;
269 this.setConnectionAllowedIfPossible();
270 this.resetFirstConnectionStatus();
271 throw err;
272 },
273 );
274 }
275
276 private handleStartDebugSession(debugSession: vscode.DebugSession): void {
277 if (
278 debugSession.configuration.rnDebugSessionId === this.rnSession.sessionId &&
279 debugSession.type === this.pwaNodeSessionName
280 ) {
281 this.nodeSession = debugSession;
282 }
283 }
284
285 private handleTerminateDebugSession(debugSession: vscode.DebugSession): void {
286 if (
287 debugSession.configuration.rnDebugSessionId === this.rnSession.sessionId &&
288 debugSession.type === this.pwaNodeSessionName
289 ) {
290 if (this.debugSessionStatus === DebugSessionStatus.ConnectionPending) {
291 this.establishDebugSession(this.previousAttachArgs);
292 } else {
293 void this.terminate();
294 }
295 }
296 }
297
298 private setConnectionAllowedIfPossible(): void {
299 if (
300 this.debugSessionStatus === DebugSessionStatus.ConnectionDone ||
301 this.debugSessionStatus === DebugSessionStatus.ConnectionFailed
302 ) {
303 this.debugSessionStatus = DebugSessionStatus.ConnectionAllowed;
304 }
305 }
306
307 private resetFirstConnectionStatus(): void {
308 if (this.debugSessionStatus === DebugSessionStatus.FirstConnectionPending) {
309 this.debugSessionStatus = DebugSessionStatus.FirstConnection;
310 }
311 }
312}