microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.11.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/rnDebugSession.ts

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