microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-peq/remove-ios-relative-project-path

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/rnDebugSession.ts

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