microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
test-microbuild1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/rnDebugSession.ts

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