microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4cd259621ddfbd348fade892a2f3ee87fd1924c5

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/rnDebugSession.ts

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