microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2c19da7f131d11b4265a94fe25139194a565116e

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/rnDebugSession.ts

234lines · 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 { DebugSessionBase, DebugSessionStatus, IAttachRequestArgs, ILaunchRequestArgs } from "./debugSessionBase";
14import * as nls from "vscode-nls";
15const localize = nls.loadMessageBundle();
16
17export class RNDebugSession extends DebugSessionBase {
18
19 private readonly terminateCommand: string;
20 private readonly pwaNodeSessionName: string;
21
22 private nodeSession: vscode.DebugSession | null;
23 private onDidStartDebugSessionHandler: vscode.Disposable;
24 private onDidTerminateDebugSessionHandler: vscode.Disposable;
25
26 constructor(session: vscode.DebugSession) {
27 super(session);
28
29 // constants definition
30 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
31 this.pwaNodeSessionName = "pwa-node"; // the name of node debug session created by js-debug extension
32
33 // variables definition
34 this.onDidStartDebugSessionHandler = vscode.debug.onDidStartDebugSession(
35 this.handleStartDebugSession.bind(this)
36 );
37
38 this.onDidTerminateDebugSessionHandler = vscode.debug.onDidTerminateDebugSession(
39 this.handleTerminateDebugSession.bind(this)
40 );
41 }
42
43 protected launchRequest(response: DebugProtocol.LaunchResponse, launchArgs: ILaunchRequestArgs, request?: DebugProtocol.Request): Promise<void> {
44 return new Promise<void>((resolve, reject) => this.initializeSettings(launchArgs)
45 .then(() => {
46 logger.log("Launching the application");
47 logger.verbose(`Launching the application: ${JSON.stringify(launchArgs, null , 2)}`);
48
49 this.appLauncher.launch(launchArgs)
50 .then(() => {
51 return this.appLauncher.getPackagerPort(launchArgs.cwd);
52 })
53 .then((packagerPort: number) => {
54 launchArgs.port = launchArgs.port || packagerPort;
55 this.attachRequest(response, launchArgs).then(() => {
56 resolve();
57 }).catch((e) => reject(e));
58 })
59 .catch((err) => {
60 logger.error("An error occurred while launching the application. " + err.message || err);
61 reject(err);
62 });
63 }));
64 }
65
66 protected attachRequest(response: DebugProtocol.AttachResponse, attachArgs: IAttachRequestArgs, request?: DebugProtocol.Request): Promise<void> {
67 let extProps = {
68 platform: {
69 value: attachArgs.platform,
70 isPii: false,
71 },
72 };
73
74 this.previousAttachArgs = attachArgs;
75 return new Promise<void>((resolve, reject) => this.initializeSettings(attachArgs)
76 .then(() => {
77 logger.log("Attaching to the application");
78 logger.verbose(`Attaching to the application: ${JSON.stringify(attachArgs, null , 2)}`);
79 return ProjectVersionHelper.getReactNativeVersions(attachArgs.cwd, true)
80 .then(versions => {
81 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeVersion, "reactNativeVersion", extProps);
82 if (!ProjectVersionHelper.isVersionError(versions.reactNativeWindowsVersion)) {
83 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeWindowsVersion, "reactNativeWindowsVersion", extProps);
84 }
85 return TelemetryHelper.generate("attach", extProps, (generator) => {
86 attachArgs.port = attachArgs.port || this.appLauncher.getPackagerPort(attachArgs.cwd);
87 this.appLauncher.getRnCdpProxy().stopServer();
88 return this.appLauncher.getRnCdpProxy().initializeServer(new RnCDPMessageHandler(), this.cdpProxyLogLevel)
89 .then(() => {
90 logger.log(localize("StartingDebuggerAppWorker", "Starting debugger app worker."));
91
92 const sourcesStoragePath = path.join(this.projectRootPath, ".vscode", ".react");
93 // Create folder if not exist to avoid problems if
94 // RN project root is not a ${workspaceFolder}
95 mkdirp.sync(sourcesStoragePath);
96
97 // If launch is invoked first time, appWorker is undefined, so create it here
98 this.appWorker = new MultipleLifetimesAppWorker(
99 attachArgs,
100 sourcesStoragePath,
101 this.projectRootPath,
102 undefined
103 );
104 this.appLauncher.setAppWorker(this.appWorker);
105
106 this.appWorker.on("connected", (port: number) => {
107 logger.log(localize("DebuggerWorkerLoadedRuntimeOnPort", "Debugger worker loaded runtime on port {0}", port));
108
109 this.appLauncher.getRnCdpProxy().setApplicationTargetPort(port);
110
111 if (this.debugSessionStatus === DebugSessionStatus.ConnectionPending) {
112 return;
113 }
114
115 if (this.debugSessionStatus === DebugSessionStatus.FirstConnection) {
116 this.debugSessionStatus = DebugSessionStatus.FirstConnectionPending;
117 this.establishDebugSession(resolve);
118 } else if (this.debugSessionStatus === DebugSessionStatus.ConnectionAllowed) {
119 if (this.nodeSession) {
120 this.debugSessionStatus = DebugSessionStatus.ConnectionPending;
121 this.nodeSession.customRequest(this.terminateCommand);
122 }
123 }
124 });
125 return this.appWorker.start();
126 });
127 })
128 .catch((err) => {
129 logger.error("An error occurred while attaching to the debugger. " + err.message || err);
130 reject(err);
131 });
132 });
133 }));
134 }
135
136 protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments, request?: DebugProtocol.Request): void {
137 // The client is about to disconnect so first we need to stop app worker
138 if (this.appWorker) {
139 this.appWorker.stop();
140 }
141
142 this.appLauncher.getRnCdpProxy().stopServer();
143
144 this.onDidStartDebugSessionHandler.dispose();
145 this.onDidTerminateDebugSessionHandler.dispose();
146
147 // Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
148 if (this.previousAttachArgs.platform === "android") {
149 try {
150 this.appLauncher.stopMonitoringLogCat();
151 } catch (err) {
152 logger.warn(localize("CouldNotStopMonitoringLogcat", "Couldn't stop monitoring logcat: {0}", err.message || err));
153 }
154 }
155
156 super.disconnectRequest(response, args, request);
157 }
158
159 private establishDebugSession(resolve?: (value?: void | PromiseLike<void> | undefined) => void): void {
160 const attachArguments = {
161 type: "pwa-node",
162 request: "attach",
163 name: "Attach",
164 continueOnAttach: true,
165 port: this.appLauncher.getCdpProxyPort(),
166 smartStep: false,
167 // The unique identifier of the debug session. It is used to distinguish React Native extension's
168 // debug sessions from other ones. So we can save and process only the extension's debug sessions
169 // in vscode.debug API methods "onDidStartDebugSession" and "onDidTerminateDebugSession".
170 rnDebugSessionId: this.session.id,
171 };
172
173 vscode.debug.startDebugging(
174 this.appLauncher.getWorkspaceFolder(),
175 attachArguments,
176 this.session
177 )
178 .then((childDebugSessionStarted: boolean) => {
179 if (childDebugSessionStarted) {
180 this.debugSessionStatus = DebugSessionStatus.ConnectionDone;
181 this.setConnectionAllowedIfPossible();
182 if (resolve) {
183 this.debugSessionStatus = DebugSessionStatus.ConnectionAllowed;
184 resolve();
185 }
186 } else {
187 this.debugSessionStatus = DebugSessionStatus.ConnectionFailed;
188 this.setConnectionAllowedIfPossible();
189 this.resetFirstConnectionStatus();
190 throw new Error("Cannot start child debug session");
191 }
192 },
193 err => {
194 this.debugSessionStatus = DebugSessionStatus.ConnectionFailed;
195 this.setConnectionAllowedIfPossible();
196 this.resetFirstConnectionStatus();
197 throw err;
198 });
199 }
200
201 private handleStartDebugSession(debugSession: vscode.DebugSession) {
202 if (
203 debugSession.configuration.rnDebugSessionId === this.session.id
204 && debugSession.type === this.pwaNodeSessionName
205 ) {
206 this.nodeSession = debugSession;
207 }
208 }
209
210 private handleTerminateDebugSession(debugSession: vscode.DebugSession) {
211 if (
212 debugSession.configuration.rnDebugSessionId === this.session.id
213 && this.debugSessionStatus === DebugSessionStatus.ConnectionPending
214 && debugSession.type === this.pwaNodeSessionName
215 ) {
216 this.establishDebugSession();
217 }
218 }
219
220 private setConnectionAllowedIfPossible(): void {
221 if (
222 this.debugSessionStatus === DebugSessionStatus.ConnectionDone
223 || this.debugSessionStatus === DebugSessionStatus.ConnectionFailed
224 ) {
225 this.debugSessionStatus = DebugSessionStatus.ConnectionAllowed;
226 }
227 }
228
229 private resetFirstConnectionStatus(): void {
230 if (this.debugSessionStatus === DebugSessionStatus.FirstConnectionPending) {
231 this.debugSessionStatus = DebugSessionStatus.FirstConnection;
232 }
233 }
234}
235